Fastest switching between comparator and ADC using same input

I want to set up the internal Analog Comparator (AC) to trigger an interrupt on a rising edge when it occurs, then switch to the Analog to Digital Converter (ADC) to measure how far the edge rises. And I only have 50us to do it.
I can do this (just) with a sequence of turn off and turn on direct hardware instructions (machine code), but looking at the ATMega328P documentation I'm wondering if the hardware designers envisioned just this usage and designed the connections between the AC and ADC to do this with some built in short cuts. Any hardware gurus who know this to be the case?

Why do I think there's a hardware short cut?

  1. Both the AC and the ADC may use the analog MUltipleXer (MUX) to select an input, the AC stores the analog input's address in ADCSRB while the ADC uses ADMUX.
  2. The ADC uses an Enable bit (1=On) while the AD uses a Disable bit (1=OFF).
  3. The ADC uses the AC to decide if each bit of the result will be a 1 or a 0 so the Disable MAY not be a power_down control but a re-direction of the AC inputs to the ADC input and the ADC comparison values (generated by an internal DAC).
  4. The ADC documentation implies that turning the ADC On automatically turns the AC Off. Reading between the lines this suggests that the AD Disable doesn't need to be toggled, just toggling the ADC Enable will "swap them over". But it also expicitly states that setting A Disable will power it down (ATMega328P Datasheet pdf p247).

So anyone know if I can leave everything set up and just toggle ADC Enable to do the swap in just a few ns?


It's not a direct answer, but if you are not using pin AIN1 for anything else, maybe you could wire the trigger source to that and, additionally, to an analog pin for the analog readings. Presumably your are then going to read samples until the rate of increase is 0.

I've done a few experiments with the Analog Comparator but it is very poorly documented, at least the electrical characteristics of the ATmega328p version. The newer series, MegaAVR series 0 like on the Nano Every are much better.

No spare pins. That's partly why the rising edge and top level need to be at the same pin.

What's the project the code is for ?
A DCC Circuit Breaker.

Digital Command (and) Control is a (30 years old) system for controlling multiple model trains on a layout.
Power is continually applied in the form of a floating square wave, trains rectify and store this, then run their motors, lights, etc from it.
Data is encoded into the square wave as variable widths (time periods). A rise, 58us hold, fall and 58us hold is a data '1',
while a data '0' is a rise, 100us hold, fall and 100us hold. At least 12 '1's separate data packets.
So all '1's are 8.6kHz, all '0's 5kHz, with the encoding scheme 'weighted' to achieve 8kHz average baud rate.

To detect a short circuit a Circuit Breaker needs to sample 'in the middle' of the 58us after each rise.
An ATMega328P with its ADC clock at 1MHz is only guarenteed acurrate to 8 bits and takes 25us from ADC Enable to complete a first reading.
Subsequent readings then take about 13us each, thus ony 3 readings are possible inside 58us. (first reading discarded as per manufacturer recomendation).


Here are some relevent code snippets -

void Setup()

// pin stuff goes here ...

// ADC
//ADCSRA |= (1 << ADEN);	// bit 7 enable ADC (turn on hardware), disables comparator hardware while On
//ADCSRA |= (1 << ADSC);	// bit 6 start ADC measurements (run).  Resets to 0 when completed.  Auto trigger will set it too.
//ADCSRA |= (1 << ADATE);	// bit 5 enable auto trigger.  Select source in ADCSRB.  Source 0 is free running, uses 'ADC has finished' interrupt 
  ADCSRA &= (1 << ADIF);	// bit 4 completion flag.  May be polled to see if conversion has finished.  Clear it just in case ...
  ADCSRA |= (1 << ADIE);	// bit 3 trigger an ISR interrupt when conversion completes.  Noise reduction Sleep mode won't wake if not set
  ADCSRA |= (1 << ADPS2);	// bit 2-0 set pre scale.  Here using 100 = 2^3 = 16 prescaler for ADC clock = system clock / 16 = 1MHz.
//ADCSRA |= (1 << ADTS1);	//         1MHz / 13 = 76,900 reads per second
//ADCSRA |= (1 << ADTS1);

// ADCSRB will only be examined during ADC setup if ADATE (auto trigger) is set in ADCSRA
				// bit 7 reserved
//ADCSRB |= (1 << ACME);	// bit 6 = register is used by Analog_Comparator rather than ADC, only examined when AC Disable is turned Off
				// bit 5 reserved
				// bit 4 reserved
				// bit 3 reserved
//ADCSRB |= (1 << ADTS2);	// bits 2-0 ADC Auto Trigger or Comparator negative input Source pin selection
//ADCSRB |= (1 << ADTS1);	// 
//ADCSRB |= (1 << ADTS0);

//ADMUX |= (1 << REFS1);	// set bit 7 reference voltage select 1 for 1.1V (needs REFS0 on too) Both off = Vext, both on = 1.1V
  ADMUX |= (1 << REFS0);	// set bit 6 reference voltage select 1 for system voltage (needs cap at Aref, Arduino has this)
  ADMUX |= (1 << ADLAR);	// set bit 5 left align ADC value so hi 8 bits go into ADCH register
				// bit 4 reserved
//ADMUX |= (1 << MUX3);		// bit 3-0 select input pin or internal sources (for testing). Range 0 - 15
  ADMUX |= (1 << MUX2);		//         This is a 'shadow' that is copied to the matrix controller at each ADC trigger.
  ADMUX |= (1 << MUX1);		//         A7 = 0111
  ADMUX |= (1 << MUX0);

  SMCR |= (1 << SM0);		// Set sleep mode to ADC Noise Reduction, ready to use later
// When ADC is On and sleep mode is turned On processor stops and ADC will start.
// When ADC completes processor wakes and jumps to ISR(ADC_vect) <-- MUST exist, even if empty
//ACSR |= (1 << ACD);     	// bit 7 Comparator Off/On (reversed, 1 = Off)
  ACSR |= (1 << ACBG);    	// bit 6 1.1V reference Off/On (takes a while to stabilize, typically 70us)
//ACSR bit ACO            	// bit 5 Comparator output value.  read_only, also sent direct to TMR1 input mux
  ACSR |= (1 << ACI);     	// bit 4 clear Analog Comparator interrupt flag (auto cleared if ISR used) (reversed, 1=clear)
//ACSR |= (1 << ACIE);    	// bit 3 Comparator Interrupt enable (also requires global ints On)
//ACSR |= (0 << ACIC);    	// bit 2 switch TMR1 input mux to receive ACO (also needs enable set in TIMSK1)
  ACSR |= (1 << ACIS1);   	// bit 1 set interrupt bit on, 0 0 = both edges, 1 x select edge ...
//ACSR |= (0 << ACIS0);   	// bit 0                       ACIS0 == 0 for falling edge, 1 == for rising)

// ADC Setup Register B may hold mux selections for comparator (setup A is for ADC), it will only be used if ACME is set
				// bit 7 reserved
  ADCSRB |= (1<<ACME);    	// switch comparator negative input from D7 pin to Analog input pin through mux
				// bit 5 reserved
				// bit 4 reserved
				// bit 3 reserved
//ADCSRB |= (1 << ADTS2);	// bits 2-0 mux address. Range 0 - 7
//ADCSRB |= (1 << ADTS1);	//         This is a 'shadow' that is copied to the matrix controller at each switch On.
//ADCSRB |= (1 << ADTS0);	//         A7 = 111
  ADCSRB |= DCCin;	  	// fastest setting is to OR the pin number
void readpots()
{                           // overheads + 2 reads takes ~40us
							// Sleep mode is set to 'ADC with noise reduction' and ADMUX is preset to DCCin before entry.
//ACSR   |= (1 << ACD);	    // turn Off comparator hardware (0=On)
  ADCSRA |= (1 << ADEN);    // Turn On ADC.  Comparator is forced Off.  For the first conversion after turn On the
                            // enable steps take an extra 12 = 25 ADC clocks, then 13/14 for successive conversions.
  SMCR   |= (1 << SE);      // Set Sleep Enabled.  Do Sleep starts an ADC.  ADC interrupt wakes processor from Sleep.
  sleep_cpu();              // Read_in completed after 14us, conversion completed after 25us. Ignore first capture, start another one.
  sleep_cpu();              // Read_in completed after 2us, conversion completed after 14us.
  adcvalues[currentload] = ADCH;     // ~39us has elapsed, store this reading in global array
//  sleep_cpu();			// take 3rd reading if enhanced accuracy required. Read_in completed after 2us at 41us.
//  adcvalues[currentload] = (adcvalues[currentload] + ADCH) / 2;     // ~50us has elapsed, average this reading with global array

  SMCR   &= (1 << SE);      // Set sleep is NOT enabled to avoid unwanted sleep triggers
  ADCSRA &= (1 << ADEN);    // turn Off ADC, this returns MUX control to the comparator through ADCSRB
//  ACSR   &= (1 << ACD);	// turn on comparator hardware (0=On), this gives mux input connection to the comparator

  ADMUX  |= DCCin;	    	// reset to DCC address for next entry

  DCCflag = 0;


I think I've understood DCC power control in that the tracks are swapping polarity continuously and the timings of the polarity swaps are used as a digital signal. I guess the detection of the normal digital signal could, for example, be done with two opto-couplers with their diodes inverted relative to each other.
It is not clear to me, though, how you distinguish between a normal load and a fault load based on the impact on timings alone. I suppose I'd have to see an oscilloscope picture of a fault to understand it. Normally, a threshold fault current would be set and the power draw constantly measured, tripping a breaker if required.

If what you are looking at is the "analog" properties of the signal, you also have the option of simply driving the ADC using a timer. In the interrupt service routine of the ADC, which would be triggered at the end of processing each sample, you could check for an abnormal voltage / time profile, in a continuous monitoring operation, and act accordingly.

What circuit, incidentally, are you using to feed the comparator and analog pins ? Simply a bridge rectifier from the tracks and a voltage divider to keep within the ADC tolerance ?

More application info -

The DCC signal timing specs have strict rise and fall requirements but allow nearly 6% variations in the hold times. Regular sampling times WILL work, but require lots of processing to reject outliers and adequately smooth the captured results, taking up way too much time.

For a DCC circuit breaker we're not looking at a couple of mA instant change, rather a few A for a prolonged period (0.1s or more). User set points and simple algorithms calculate the exact requirements.

I'm using a Current Transformer to capture the rising edges, then a Burden Resistor to convert the current to a voltage (set to max 100mV) then a x50 op amp to the analog In pin (protected from overvoltage with a diode to +5V). The inductor action of the secondary coil in the Current Transformer 'holds' the peak signal for ~50us while the op amp provides a strong (low impedence) drive into the AC and ADC.

So monitoring for the rise then quickly sampling is what works.
As the inductor peak 'hold' decays ~2% in 50us, then drops rapidly to 0 over the next few us quick reaction IS definately needed. Every ns counts.


This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.