Speed up arduino

PaulS:
It's like cutting the handle off a toothbrush before putting it in the backpack next to the cast iron dutch oven and canned beans.

You know, I think we are all on a roll today. Must be something in the air. :wink:

hong89:
Hi Nick Gammon,

Sorry that I'm not understand about the code
...
How can I combine the code as given by you?

I'm not sure I understand the question. Given the figures we've been discussing what is your objective? You can't argue with the laws of physics. Well, you can, but you won't win.

You can't argue with the laws of physics.

In my head, I'm hearing James Doohan...

I look and I look and I see here's something else I hadn't seen before. Until now, to me, interrupts need attachInterrupt() to work... which is not correct.
So I dig and see that when I get back later I have some reading to do:
http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Just one tiny inconsequential nit-pick because it -might- have a tiny chance to speed your code;
why combine the low and high bytes in the IRQ() when you're only going to split them out to print bytes in loop()?

You can't argue with the laws of physics.

Then science really isn't a matter of opinion? ]:smiley:

I see here's something else I hadn't seen before. Until now, to me, interrupts need attachInterrupt() to work ...

Indeed not.

Although since attachInterrupt already exists in the library, you need to use that for INT0_vect and INT1_vect or you are likely to get a duplicate symbol linker error. Hmmm ... does a test ... maybe not.

Just one tiny inconsequential nit-pick because it -might- have a tiny chance to speed your code;
why combine the low and high bytes ...

Because of the comments in analogRead, which I reproduced.

Datasheet, page 251:

ADCL must be read first, then ADCH, to ensure that the content of the Data Registers belongs to the same conversion. Once ADCL is read, ADC access to Data Registers is blocked.

Hi Hong89

What are you making and why do you need to sample with 2kHz?

Let's look at your sampling and calculations. This is more or less a test run with 10000 samples:

int fsrPin = 0;
int fsrReading, fsrVoltage;
unsigned long fsrResistance, fsrConductance;
long fsrForce;

unsigned long time1, time2;
unsigned long loopNumber= 10000;

void setup(){
  Serial.begin(9600);
  time1 = micros();
  for(long i = 0; i < loopNumber; i++){
    fsrReading = analogRead(fsrPin);
    fsrVoltage = map(fsrReading, 0, 1023, 0, 5000);
  
    fsrResistance = 5000-fsrVoltage;
    fsrResistance *= 10000;
    fsrResistance /=fsrVoltage;
    
    fsrConductance = 1000000 / fsrResistance;                  // don't divide by 0 - add check!
    
    if (fsrConductance <= 100) fsrForce = fsrConductance / 20; // be aware some bad integer math can happen here
    if (fsrConductance >  100) fsrForce = fsrConductance / 50; // same same problem
  }
  time2=micros();
  Serial.println((time2-time1)*1.0/loopNumber);         // uS
  Serial.println(1000.0/((time2-time1)/loopNumber));    // kHz
}

void loop(){
}

Result:

281.33 uS
3.56 kHz

3.5 kHz is not that bad but there is problems in your code. This one:

 fsrVoltage = map(fsrReading, 0, 1023, 0, 5000);

is draining time

There is a potential risk of divide by zero here:

fsrConductance = 1000000 / fsrResistance;

If you read 5V on the pin fsrResistance = 0.

You are loosing most of the accuracy of your reading and calculations here:

fsrForce = fsrConductance / 20;

First thing first. Let's drop the map function. 5000 / 1023 is more or less 5. We keep integer math for now:

int fsrPin = 0;
int fsrReading, fsrVoltage;
unsigned long fsrResistance, fsrConductance;
long fsrForce;

unsigned long time1, time2;
unsigned long loopNumber= 10000;

void setup(){
  Serial.begin(9600);
  time1 = micros();
  for(long i = 0; i < loopNumber; i++){
    fsrReading = analogRead(fsrPin);
//  fsrVoltage = map(fsrReading, 0, 1023, 0, 5000);
    fsrVoltage = fsrReading * 5;

    fsrResistance = 5000-fsrVoltage;
    fsrResistance *= 10000;
    fsrResistance /=fsrVoltage;
    
    fsrConductance = 1000000 / fsrResistance;                  // don't divide by 0 - add check!
    
    if (fsrConductance <= 100) fsrForce = fsrConductance / 20; // be aware some bad integer math can happen here
    if (fsrConductance >  100) fsrForce = fsrConductance / 50; // same same problem
  }
  time2=micros();
  Serial.println((time2-time1)*1.0/loopNumber);         // uS
  Serial.println(1000.0/((time2-time1)/loopNumber));    // kHz
}

void loop(){
}

Output:

232.01 uS
4.31 mHz

Wow - 750 Hz up - let's how much we loose on float math:

int fsrPin = 0;
int fsrReading;
float fsrVoltage, fsrResistance, fsrConductance, fsrForce;

unsigned long time1, time2;
unsigned long loopNumber= 10000;

void setup(){
  Serial.begin(9600);
  time1 = micros();
  for(long i = 0; i < loopNumber; i++){
    fsrReading = analogRead(fsrPin);
    fsrVoltage = fsrReading*(5000.0/1023.0);
  
    fsrResistance = 5000.0-fsrVoltage;
    fsrResistance *= 10000.0;
    fsrResistance /=fsrVoltage;
    
    fsrConductance = 1000000.0 / fsrResistance;  // don't divide by 0 add check!
    
    if (fsrConductance <= 100) fsrForce = fsrConductance / 20.0; 
    if (fsrConductance >  100) fsrForce = fsrConductance / 50.0;
  }
  time2=micros();
  Serial.println((time2-time1)*1.0/loopNumber);
  Serial.println(1000.0/((time2-time1)/loopNumber));
}

void loop(){
}

Result:

240.05 uS
4.17 kHz

Only 140 Hz down. Not a lot.

Well, time to bring in the big canon. I did a search for "Faster analog read", and to be honest I don't get much of it except: Yeah -prescale can be set to 16 with no significant loss of quality. Cut and paste code from that thread:

// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

int fsrPin = 0;
int fsrReading;
float fsrVoltage, fsrResistance, fsrConductance, fsrForce;

unsigned long time1, time2;
unsigned long loopNumber= 10000;

void setup(){
  // set prescale to 16
  sbi(ADCSRA,ADPS2) ;
  cbi(ADCSRA,ADPS1) ;
  cbi(ADCSRA,ADPS0) ;
  
  Serial.begin(9600);
  time1 = micros();
  for(long i = 0; i < loopNumber; i++){
    fsrReading = analogRead(fsrPin);
    fsrVoltage = fsrReading*(5000.0/1023.0);
  
    fsrResistance = 5000.0-fsrVoltage;
    fsrResistance *= 10000.0;
    fsrResistance /=fsrVoltage;
    
    fsrConductance = 1000000.0 / fsrResistance;  // don't divide by 0 add check!
    
    if (fsrConductance <= 100) fsrForce = fsrConductance / 20.0; 
    if (fsrConductance >  100) fsrForce = fsrConductance / 50.0;
  }
  time2=micros();
  Serial.println((time2-time1)*1.0/loopNumber);
  Serial.println(1000.0/((time2-time1)/loopNumber));
}

void loop(){
}

Result:

142.81 uS
7.04 kHz

Wow, that kinda did the trick. Even with 3 * analogRead you should be surfing above the 2 kHz level.

-Fletcher

I got with your code, Fletcher Chr:

144.33 uS
6.94 kHz

Now if you combine your code with the idea of doing the ADC conversions in the background you can get even faster:

// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

int fsrPin = 0;
int fsrReading;
float fsrVoltage, fsrResistance, fsrConductance, fsrForce;

unsigned long time1, time2;
unsigned long loopNumber= 10000;

volatile int adcReading;
volatile boolean adcDone;

// ADC complete ISR
ISR (ADC_vect)
  {
  byte low, high;
  
  // we have to read ADCL first; doing so locks both ADCL
  // and ADCH until ADCH is read.  reading ADCL second would
  // cause the results of each conversion to be discarded,
  // as ADCL and ADCH would be locked when it completed.
  low = ADCL;
  high = ADCH;

  adcReading = (high << 8) | low;
  adcDone = true;  
  }  // end of ADC_vect
  
void setup(){
  // set prescale to 16
  sbi(ADCSRA,ADPS2) ;
  cbi(ADCSRA,ADPS1) ;
  cbi(ADCSRA,ADPS0) ;

  // set the analog reference (high two bits of ADMUX) and select the
  // channel (low 4 bits).  this also sets ADLAR (left-adjust result)
  // to 0 (the default).
  ADMUX = _BV (REFS0) | (fsrPin & 0x07);
  
  Serial.begin(115200);
  time1 = micros();
  
  // start first conversion
  ADCSRA |= _BV (ADSC) | _BV (ADIE);
  
}

long count = 0;

void loop(){
  
  // if last reading finished, process it
  if (adcDone)
    {
    // save previous reading
    fsrReading = adcReading;
    adcDone = false;
    
    // start next conversion
    ADCSRA |= _BV (ADSC) | _BV (ADIE);

    fsrVoltage = fsrReading*(5000.0/1023.0);
  
    fsrResistance = 5000.0-fsrVoltage;
    fsrResistance *= 10000.0;
    fsrResistance /=fsrVoltage;
    
    fsrConductance = 1000000.0 / fsrResistance;  // don't divide by 0 add check!
    
    if (fsrConductance <= 100) fsrForce = fsrConductance / 20.0; 
    if (fsrConductance >  100) fsrForce = fsrConductance / 50.0;

    count++;
    
    if (count >= loopNumber)
      {
      time2=micros();
      Serial.println((time2-time1)*1.0/loopNumber);
      Serial.println(1000.0/((time2-time1)/loopNumber));
      while (true) {}  // now just loop 
      }  // end done required number
      
    }  // end last reading done
  
}  // end of loop

Results:

101.08 uS
9.90 kHz

That saved 44 uS by doing those calculations while the ADC was taking the next reading.

And without fiddling with the prescaler:

int fsrPin = 0;
int fsrReading;
float fsrVoltage, fsrResistance, fsrConductance, fsrForce;

unsigned long time1, time2;
unsigned long loopNumber= 10000;

volatile int adcReading;
volatile boolean adcDone;

// ADC complete ISR
ISR (ADC_vect)
  {
  byte low, high;
  
  // we have to read ADCL first; doing so locks both ADCL
  // and ADCH until ADCH is read.  reading ADCL second would
  // cause the results of each conversion to be discarded,
  // as ADCL and ADCH would be locked when it completed.
  low = ADCL;
  high = ADCH;

  adcReading = (high << 8) | low;
  adcDone = true;  
  }  // end of ADC_vect
  
void setup(){
  // set the analog reference (high two bits of ADMUX) and select the
  // channel (low 4 bits).  this also sets ADLAR (left-adjust result)
  // to 0 (the default).
  ADMUX = _BV (REFS0) | (fsrPin & 0x07);
  
  Serial.begin(115200);
  time1 = micros();
  
  // start first conversion
  ADCSRA |= _BV (ADSC) | _BV (ADIE);
}  // end of setup

long count = 0;

void loop(){
  
  // if last reading finished, process it
  if (adcDone)
    {
    // save previous reading
    fsrReading = adcReading;
    adcDone = false;
    
    // start next conversion
    ADCSRA |= _BV (ADSC) | _BV (ADIE);

    fsrVoltage = fsrReading*(5000.0/1023.0);
  
    fsrResistance = 5000.0-fsrVoltage;
    fsrResistance *= 10000.0;
    fsrResistance /=fsrVoltage;
    
    fsrConductance = 1000000.0 / fsrResistance;  // don't divide by 0 add check!
    
    if (fsrConductance <= 100) fsrForce = fsrConductance / 20.0; 
    if (fsrConductance >  100) fsrForce = fsrConductance / 50.0;

    count++;
    
    if (count >= loopNumber)
      {
      time2=micros();
      Serial.println((time2-time1)*1.0/loopNumber);
      Serial.println(1000.0/((time2-time1)/loopNumber));
      while (true) {}  // now just loop 
      }  // end done required number
      
    }  // end last reading done
  
}  // end of loop

Results:

121.74 uS
8.26 kHz

Does CPU activity affect ADC accuracy much?

I am guessing not, because the datasheet describes the interrupt processing in some detail. The "accuracy" part does not mention CPU activity as making it less accurate, excepting that you get less noise if you put the processor to sleep during the conversion.

In fact what you could do to slightly improve the throughput is to make each conversion automatically trigger a new one (there is a flag for that) so you don't wait the handful of clock cycles to manually start the next. But I don't think it would make a heap of difference in this case.

Particularly as the OP is limited by data transfer speeds to the host computer.

That's the part that I wondered about, but it must be for extra-special measurements.

Maybe the OP could get a sound chip (or chip set) to give 22k-44k 16-bit sampling? Could an UNO, especially with the serial bottleneck, even keep up with that?

Doubt it. 10K with the normal prescaler would be the limit, and even adjusting the prescaler would only make a modest difference. Fletcher's change only took us from 4 KHz to 7 KHz. Not a huge amount. I think my change with the calculating in the background, where you got up to 9.9 KHz is probably about the max.

Bear in mind we were playing with an ADC chip a week or so ago in another thread on the forum. That converted at about the rate you read from it using SPI, so that was about 4 uS or so per reading.

Hi Nick

I did the analysis to see where time was draining away. It surprised me that the map function was takeing so much time. With the map(myValue, 0,A,0,B) the conversion would be a simple B/A ratio. I might look into the engine room of the function to see how much optimization could be applyed.

I'm not sure why Hong89 needs to sample with 2 kHz. In this thread

He is pointing to a Force Sensor Resistor:
http://dshop.ch/osc/product_info.php?cPath=23_37&products_id=109375

where they write:

These sensors are simple to set up and great for sensing pressure, but they aren't incredibly accurate. Use them to sense if it's being squeezed, but you may not want to use it as a scale.

I guess a change of pree scale won't harm the readings :stuck_out_tongue:
We are not talking high res super fast sound analysis here. So why the 2 kHz issue?

It's also hard to figure out what the force result from the 3 sensors are used for. Motorcontrole? datalogging?

-Fletcher

Sorry for the misleading.

My whole project is building an automated system with realtime feedback from the force sensors and hall sensor.
The system is build in matlab simulink already and now I am code it into arduino. It's important to have the feedback from the force sensor as fast as possible to allow the system to responce immediately. By the time, the sampling rate have to be high to avoid of lost counting from tha hall sensor. The position feedback is a main criteria of the system as well.

Now I'm trying with the code from Nick Gammon and Fletcher to combine with the main code of my system.

Many thanks for the help =)

hong89:
Now I'm trying with the code from Nick Gomman ...

Never heard of the fella.

Sorry for the typo. :fearful:

Hi

It's important to have the feedback from the force sensor as fast as possible to allow the system to responce immediately. By the time, the sampling rate have to be high to avoid of lost counting from tha hall sensor.

So I guess the magnet is mounted on a rotating shaft - well how fast is this shaft rotating? 1-2 kHz is rather fast. I have seen 2-stroke motors for RC model airplanes rotate at this speed, but it's a rare speed with substantial shaft mass.

Do you need to export the force sensor and hall count in real time to matlab simulink or do you need to export at a certain event like: The force is above a critical value?

-Fletcher

Might be better to connect the Hall to an interrupt and send micros since last turn, 16-bit.
Might be better to only send force when it changes past a threshold more than sensor error.

2 kHz is fast? I had a Yamaha 650cc cruiser up to 10.5k more than a couple times, about 120 mph. That's nothing compared to a decent crotch rocket.

2 kHz is fast? I had a Yamaha 650cc cruiser up to 10.5k more than a couple times, about 120 mph

Hz != RPM

Oh, you're right! Hz is per second, not per minute!