ADC Free Running Mode with ISR

Copilot recommended a single threshold with debounce for detection but I realize that is throwing data away. So I will use 2 thresholds, a higher initial threshold and a lower qualified threshold.

Once the signal drops below the initial threshold, I start counting, once above, I stop. If the qualifier threshold is met, then I know the car passed overhead. I can use the count of cycles to determine speed of the 2.5 inch car. (I get ms per inch and then convert to mph.)

I just discovered photo diodes. I wonder if those are better than LDR photo resistors??

The responsivity bandwidth and other characteristics of photodiodes and photoresistors are very different.

A photo transistor is generally a more direct replacement for a LDR. A photo diode is more difficult to use. Infrared proximity sensors may also be useful for object detection and possibly more reliable than using just disturbances to the ambient light.

My trap speed detector is now working great, generates mph as expected.

In essence, I measure the number of ADC conversions per millisecond which is 36. If I have two lanes, then it's 18 per second. I count the conversions and send that down into the ring buffer for the main loop to do the floating point.

However for the most part, I try to do all my math as integer based.
So instead of x *= 0.75: I do: x=(x*3) >> 2;

What is a trap speed detector?
Please show your complete code and a wiring diagram.

I have the code on GitHub as a public project. Still a work in progress. I will post the link when I'm further along. If you pm me, I'll send a link. (pm allowed here?)

This shows the wiring. I will be swapping the 6 LEDs for 2 water clear RGB LEDs.

This way I have red, yellow, green flags and maybe purple :purple_circle: for pit call? Blue for winner?

Notice no light gantry is needed, I bought a floor lamp that shines much needed light above the table. With my two threshold measuring technique, no false positives from shadows.

Or. . . White (fast blinking) ?

I’ve read only post #1.
Your ADC will run at 1MHz, resulting in 76k measurements per second. This is too fast.
Normal in Arduino is Division Factor 128 to achieve a clock speed of 125kHz, resulting in 9600 measurements per second.

1 Like

@stitech, it can read faster than 9600 per second but not as high as I am requesting.

Indeed, I need to calm that setting down as I'm currently getting 36 reads per millisecond, so 36000 per second. The while not ready loop, waits for the ADC to be ready. (This might actually be 38.5KHz giving the rounding)

ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz

So I think I should switch to the above or slower value to be more efficient than jamming up the interrupts.

Do you have a recommendation?


A speed trap for model slot car track.

I'm surprised you didn't Google;

arduino chronograph

Tom.. :smiley: :+1: :coffee: :australia:

Speed trap

/ˈspēd ˌtrap/

noun

  • 1.an area of road in which hidden police detect vehicles exceeding a speed limit, typically by radar.

I’ve been thinking about speed again.
Assume source resistance 10k, S/H capacitor = 14pF, RC time = 0.14us.
At ADC_clock = 1MHz, time for charging the S/H capacitor is: 2us.
This should be enough.

The other problem is minimizing the execution time of the ISR.
After the interrupt occurs, the current instruction is finished, a delay of 1 or 2 cycles.
Then a jump to the ISR, 3 cycles.
The ISR will at least PUSH and POP r0, r1 and status register, so at least 20 cycles overhead.
The ISR below will use at least 2 working registers, the compiler will add PUSHes and POPs for those, another 8 cycles gone.
The branch takes 1 or 2 cycles, 2 registers are read and 1 written, say 8 cycles.
Total: 41 cycles = 2.5us.
This leaves some room for operations on the results, but be careful that you leave enough time for the execution of loop().

Fastest ISR for ADC: {  // if longer than 13us, input selection will have changed 
  // ADIF is reset by hardware
  uint8_t tmp = ADCH;             // make local variable
  if (ADMUX & 0b00000001 == 0) {  // ADMUX as memory, value belongs to selected input
    ADMUX = 0b00100001;           // set for input A1, keep ADLAR, don't read-modify-write
    // operations on tmp here, mind execution time
    x0 = tmp;                     // don't use indexing, memory access only once
  } else {
    // input A1 was read
    ADMUX = 0b00100000;  // set for input A0, keep ADLAR
    // put the same operations here if needed, only one brach will be executed
    x1 = tmp;            // memory access only once
  }
}

@stitech there are many parts you missed in this conversation. To catch you up.

Here is my current Free ADC with ISR code.

//define BIT8 to have 8 bit sensors
//#define BIT8

typedef enum : uint8_t { PRESAMPLE,  GO, FINISH } IsrFlag; 
volatile IsrFlag isrFlag = PRESAMPLE;

#define NUMSENSORS 2
static_assert((NUMSENSORS & (NUMSENSORS - 1)) == 0, "NUMSENSORS must be a power of two");
//ACCSMOOTH must be base 2
#define ACCSMOOTH 1
static_assert((ACCSMOOTH & (ACCSMOOTH - 1)) == 0, "ACCSMOOTH must be a power of two");

Sensor sensors[NUMSENSORS];

class ISR{
  public:
  
  static setup(){
    ADCSRA = 0;             // clear ADCSRA register
    ADCSRB = 0;             // clear ADCSRB register
    ADMUX = (ADMUX & 0xF0) | 0x00; // We will start with pin A0
    ADMUX |= (1 << REFS0);  // set reference voltage

    #ifdef BIT8
    ADMUX |= (1 << ADLAR);  // left align ADC value to 8 bits from ADCH register
    #endif
  
    // sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles]
    // for Arduino Uno ADC clock is 16 MHz and a conversion takes 13 clock cycles
    //ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz
    ADCSRA |= (1 << ADPS2);                     // 16 prescaler for 76.9 KHz
    //ADCSRA |= (1 << ADPS1) | (1 << ADPS0);    // 8 prescaler for 153.8 KHz
        
    ADCSRA |= (1 << ADIF); // Clear ADIF by writing a 1 to it (this is how you clear the flag)
   
    ADCSRA |= (1 << ADATE); // enable auto trigger
    ADCSRA |= (1 << ADIE);  // enable interrupts when measurement complete
    ADCSRA |= (1 << ADEN);  // enable ADC
    ADCSRA |= (1 << ADSC);  // start ADC measurements
  }

  static void calcThresholds(){
    for(int i=0;i<NUMSENSORS;i++){
      Sensor& s=sensors[i];
      p("i",i);
      s.calcThreshold();
    }
  }

  static void go(){
    isrFlag=GO;
  }
  
};


uint8_t curSensor = 0;
volatile int pings=0;
volatile unsigned int notReady=0;


//// ADC interrupt service routine
ISR(ADC_vect) {
  static uint8_t accCount=0; 
  Sensor& sensor=sensors[curSensor];
  auto nextSensor=(curSensor + 1) & (NUMSENSORS-1);
  if(accCount==0){
    sensor.acc=0;
  }
  while(!(ADCSRA & (1 << ADIF))) { //wait
    //spin wheels    
  }
  ADCSRA |= (1 << ADIF); // Clear ADIF by writing a 1 to it (this is how you clear the flag)
  #ifdef BIT8
    sensor.acc += ADCH;   // Read the ADC value for 8 bit resolution
  #else
    sensor.acc += ADC; // 10 bit resolution
  #endif
  ADMUX = (ADMUX & 0xF0) | nextSensor;  // Update to the next channel
  if(accCount==ACCSMOOTH-1){ //we accumulated enough readings
    //now do the checking on this value
    auto flag=isrFlag;
    if(flag==PRESAMPLE) {
      sensor.presample();      
    }else if(flag==GO){
      sensor.go(curSensor);
    }      
  }
  if(curSensor==NUMSENSORS-1){
    accCount=(accCount+1) & (ACCSMOOTH-1);    
  }
  curSensor = nextSensor;
}

I’ll recap what I understood so far:

  • you want to go Fast
  • you want to use Free Running Mode
  • you have 2 analog inputs
  • you don’t need 10 bit precision
  • you have a cloned Arduino Mega 2560

The datasheet of the ATmega2560 gives a maximum clock speed for the ADC of 1MHz, in which case 1 conversion takes 13μs.
If a new input is selected while a conversion is running, it is delayed till the current conversion is complete.
You’ve got 13μs to read the result of a conversion before it is overwritten, and you can’t be sure which input your result came from.

Your sketch doesn’t compile, many things are not declared.
Why setup() in a Class?

In your ISR I see many things that take unnecessary time.
static uint8_t accCount=0;
The initialization at the first time needs a variable as switch, this must be read from RAM (a working register must be freed by a PUSH), followed by a test and a branch. Then the variable itself must be read from RAM.
sensor=sensors[curSensor];
The base of the table will be read from RAM. The index will be read from RAM and added. Then the value itself will be read from RAM. (Not every variable needs a new working register freed by a PUSH, the compiler will reuse whenever possible.)
while(!(ADCSRA & (1 << ADIF))) {
The Interrupt Flag is reset by hardware when the interrupt is executed, so it’s always 0, but as it is it takes a read, an operation and a branch.

And the list goes on, you'll never make it in 13μs.

Yes exactly. But luckily, I don't need 76kHz.

Btw, my project is very big, I only gave the code for ISR.h

76923 analog reads per second isn't happening now. My actual throughput is half that and the sketch is working very well noticing the car, so I should reduce the sampling rate.

This would be closer to reality:

ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz

This asks too much:

    ADCSRA |= (1 << ADPS2);                     // 16 prescaler for 76.9 KHz

This is perhaps the most important line of code, without it, everything goes to gibberish. This limits the rate to what it can actually do.
while(!(ADCSRA & (1 << ADIF))) {}

@nickgammon did some excellent testing and reporting on the Uno ADC at various prescaler speeds:

The whole post is worth reading.

2 Likes

Interesting tidbit I discovered, halving the scaler cuts the samples per second in half. So if the interrupt was too slow, it wouldn't be linear like that.

Hello, @jim-p asked for me to post my code.

Well here is my project page. Introducing Vroom Racer.

The project is playing testing well.
It has lane rider protection (when a car hops on your lane), automatic yellow flag, automatic red flag and even a competition yellow about halfway through the race.

I plan to post wiring diagrams as well.


1 Like