Skip to main content
Arduino

Return to Answer

+ address updated question.
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

Edit: Addressing the updated question.

There are a few issues I would like to address:

First, the way the updated code handles the acquisition timing is a bit convoluted. Furthermore, updating the time with prevTime = curTime; is prone to timing drift: you cannot count on curTime being exactly the time at which the ADC reading was scheduled, it can be a few microseconds later. Actually, curTime will always be later, as the acquisition period (1,666 μs) is not a multiple of the micros() resolution (4 μs). This timing drift can be avoided by making prevTime represent the time when the previous acquisition was scheduled (not when it was performed):

// Data acquisition.
unsigned long prevTime = micros();
for (int i = 0; i < size; i++) {
 while (micros() - prevTime < dly) continue; // wait until it's time
 prevTime += dly; // update scheduled time
 sensorValue[i] = analogRead(sensorPin); // acquire data
}

The limited resolution of micros() will still cause some jitter, but updating with prevTime += dly; ensures there is no systematic drift.

Second, I would avoid doing any kind of filtering on the Arduino itself. If the data is meant to be transferred to the PC anyway, just transfer the raw data. This gives you the chance of processing it on your PC, interactively experimenting with different types of filters, with the comfort of your favorite programming language or data-processing package. If the complete project requires the filtering to be done on the Arduino, do that later, once you have found the most appropriate filter on the PC.

Third, as filtering goes, you seem to have a lot of 50 Hz noise here. Maybe the noise is already present in the luminous flux, or maybe it is introduced somewhere between the photodiode and the Arduino. I would check the cabling to see whether there is a way to reduce inductive noise pickup. Are you using long cables between the photodiode and the Arduino? If so, is that a twisted pair? Is everything properly grounded? Are there any ground loops?

If you cannot get rid of the mains noise at the source, it will be very difficult to identify signals that are anywhere close to 50 Hz, as your 30 Hz experiment shows. You may want to try a notch filter specifically tuned to kill any 50 Hz component.

Fourth, you may want to try, at least once, to acquire at a faster rate: let's say with a 200 μs period. If that acquisition reveals a lot of high-frequency noise, then it may be worth using some decimating filter. For example, you could store, in each array position, the sum of eight samples taken at 200 μs intervals. That should give less noisy data, with a period of 1.6 ms per array cell (round value close to the current 1.666 ms). Note that this technique will be absolutely useless against 50 Hz noise though.

Fifth, it is possible to trigger the ADC from a timer, which gives very accurate and consistent timing. This, however, requires low-level programming, and digging into the ATmega datasheet. I would not care about perfect timings now, at least not until the above issues are fixed. It is, however, a possibility you may want to keep in mind in case in the future you want to get the best possible timings.


Edit: Addressing the updated question.

There are a few issues I would like to address:

First, the way the updated code handles the acquisition timing is a bit convoluted. Furthermore, updating the time with prevTime = curTime; is prone to timing drift: you cannot count on curTime being exactly the time at which the ADC reading was scheduled, it can be a few microseconds later. Actually, curTime will always be later, as the acquisition period (1,666 μs) is not a multiple of the micros() resolution (4 μs). This timing drift can be avoided by making prevTime represent the time when the previous acquisition was scheduled (not when it was performed):

// Data acquisition.
unsigned long prevTime = micros();
for (int i = 0; i < size; i++) {
 while (micros() - prevTime < dly) continue; // wait until it's time
 prevTime += dly; // update scheduled time
 sensorValue[i] = analogRead(sensorPin); // acquire data
}

The limited resolution of micros() will still cause some jitter, but updating with prevTime += dly; ensures there is no systematic drift.

Second, I would avoid doing any kind of filtering on the Arduino itself. If the data is meant to be transferred to the PC anyway, just transfer the raw data. This gives you the chance of processing it on your PC, interactively experimenting with different types of filters, with the comfort of your favorite programming language or data-processing package. If the complete project requires the filtering to be done on the Arduino, do that later, once you have found the most appropriate filter on the PC.

Third, as filtering goes, you seem to have a lot of 50 Hz noise here. Maybe the noise is already present in the luminous flux, or maybe it is introduced somewhere between the photodiode and the Arduino. I would check the cabling to see whether there is a way to reduce inductive noise pickup. Are you using long cables between the photodiode and the Arduino? If so, is that a twisted pair? Is everything properly grounded? Are there any ground loops?

If you cannot get rid of the mains noise at the source, it will be very difficult to identify signals that are anywhere close to 50 Hz, as your 30 Hz experiment shows. You may want to try a notch filter specifically tuned to kill any 50 Hz component.

Fourth, you may want to try, at least once, to acquire at a faster rate: let's say with a 200 μs period. If that acquisition reveals a lot of high-frequency noise, then it may be worth using some decimating filter. For example, you could store, in each array position, the sum of eight samples taken at 200 μs intervals. That should give less noisy data, with a period of 1.6 ms per array cell (round value close to the current 1.666 ms). Note that this technique will be absolutely useless against 50 Hz noise though.

Fifth, it is possible to trigger the ADC from a timer, which gives very accurate and consistent timing. This, however, requires low-level programming, and digging into the ATmega datasheet. I would not care about perfect timings now, at least not until the above issues are fixed. It is, however, a possibility you may want to keep in mind in case in the future you want to get the best possible timings.

typo: takes → taken
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

Consider this loop:

for(int i=0; i< size;i++){
 // Read sensor value
 sensorValue[i] = analogRead(sensorPin);
 // wait between readings for N microseconds
 delayMicroseconds(dly);
}

The time taken by each iteration is the sum of the times takestaken by each individual operation (compare i to size, read the ADC, delay, increment i and jump to the start of the loop). Other than the delay, the longest operation here is analogRead(), as it blocks in a busy loop while the ADC performs its conversion. This can take anywhere between 104 μs and 112 μs. Even neglecting everything but analogRead() and delayMicroseconds(), you expect your loop to be about 6–7% too slow.

You should get better timings if you use micros() instead of delayMicroseconds(). Check the Arduino tutorial "Blink without delay" to see how you can manage your timings without delaying. You still won't get metrology-class timings though, as micros() is only as good as the ceramic resonator clocking your Uno, which should be roughly 0.1% accurate in its frequency. If you need anything better, you will have to find a better frequency standard.

Consider this loop:

for(int i=0; i< size;i++){
 // Read sensor value
 sensorValue[i] = analogRead(sensorPin);
 // wait between readings for N microseconds
 delayMicroseconds(dly);
}

The time taken by each iteration is the sum of the times takes by each individual operation (compare i to size, read the ADC, delay, increment i and jump to the start of the loop). Other than the delay, the longest operation here is analogRead(), as it blocks in a busy loop while the ADC performs its conversion. This can take anywhere between 104 μs and 112 μs. Even neglecting everything but analogRead() and delayMicroseconds(), you expect your loop to be about 6–7% too slow.

You should get better timings if you use micros() instead of delayMicroseconds(). Check the Arduino tutorial "Blink without delay" to see how you can manage your timings without delaying. You still won't get metrology-class timings though, as micros() is only as good as the ceramic resonator clocking your Uno, which should be roughly 0.1% accurate in its frequency. If you need anything better, you will have to find a better frequency standard.

Consider this loop:

for(int i=0; i< size;i++){
 // Read sensor value
 sensorValue[i] = analogRead(sensorPin);
 // wait between readings for N microseconds
 delayMicroseconds(dly);
}

The time taken by each iteration is the sum of the times taken by each individual operation (compare i to size, read the ADC, delay, increment i and jump to the start of the loop). Other than the delay, the longest operation here is analogRead(), as it blocks in a busy loop while the ADC performs its conversion. This can take anywhere between 104 μs and 112 μs. Even neglecting everything but analogRead() and delayMicroseconds(), you expect your loop to be about 6–7% too slow.

You should get better timings if you use micros() instead of delayMicroseconds(). Check the Arduino tutorial "Blink without delay" to see how you can manage your timings without delaying. You still won't get metrology-class timings though, as micros() is only as good as the ceramic resonator clocking your Uno, which should be roughly 0.1% accurate in its frequency. If you need anything better, you will have to find a better frequency standard.

Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

Consider this loop:

for(int i=0; i< size;i++){
 // Read sensor value
 sensorValue[i] = analogRead(sensorPin);
 // wait between readings for N microseconds
 delayMicroseconds(dly);
}

The time taken by each iteration is the sum of the times takes by each individual operation (compare i to size, read the ADC, delay, increment i and jump to the start of the loop). Other than the delay, the longest operation here is analogRead(), as it blocks in a busy loop while the ADC performs its conversion. This can take anywhere between 104 μs and 112 μs. Even neglecting everything but analogRead() and delayMicroseconds(), you expect your loop to be about 6–7% too slow.

You should get better timings if you use micros() instead of delayMicroseconds(). Check the Arduino tutorial "Blink without delay" to see how you can manage your timings without delaying. You still won't get metrology-class timings though, as micros() is only as good as the ceramic resonator clocking your Uno, which should be roughly 0.1% accurate in its frequency. If you need anything better, you will have to find a better frequency standard.

lang-cpp

AltStyle によって変換されたページ (->オリジナル) /