Jamesik:
Is it possible to sync the analog read with the PWM output?
There's a way to do that. Here's how:
- Configure Timer1 to run with a 50 us cycle time, using ICR1 as TOP. That's mode 14, a fast PWM mode.
- Use OCR1A to set the pulse width. That restricts the output pin for PWM to pin 9, OC1A.
- Set the ADC prescaler to either 32 or 16, for an ADC clock frequency of 500 kHz or 1MHz, conversion time of 27 or 13.5 us, respectively, so that it can perform a conversion within the PWM cycle time.
- Configure the ADC to autotrigger on Timer1 Compare Match B. The time that conversions are initiated, relative to the PWM cycle, will be controlled by the value of OCR1B
- Set OCR1B to trigger a conversion at a point in the PWM cycle that you want to measure, when you can expect the analog to be stable, and at least 2.5 ADC clock cycles before the analog value will start to change. From the datasheet, the ADC's samle/hold occurs 1.5 ADC clock cycles after the conversion starts; from my tests, if the analog changes within a bit more than an ADC clock cycle of that time, the change will affect the reading.
- Acquire the analog reading in an ADC interrupt service routine, rather than using analogRead().
- There's no need for an ISR for Timer1 Compare Match B. If one exists, it will automatically reset the interrupt flag when the ISR executes; otherwise, there's nothing for it to do. Without that ISR, the ADC ISR will have to reset the Timer1 Compare Match B interrupt flag, so that its next rising edge can trigger another conversion. If the ADC ISR doesn't do this, and there's no Timer1 Compare MAtch B ISR, there will be only one ADC conversion, since the Timer1 Compare Match B interrupt flag will never have another rising edge. If you forget to do this, it will likely be hard to troubleshoot.
There are disadvantages. This method requires programming right down to the bare metal. You'll have to directly manipulate the control and status registers for the ADC and Timer1, and that will make the code non-portable. This kind of programming may be more than you want to do, considering that you say that you're new at using the Arduino.
Here's a sketch demonstrating the concept, to run on an Uno. Timer1 is set to mode 14, fast PWM with TOP=ICR1. Timer1 prescaler is set to 1, no prescaling, so the timer is clocked by the system's 16 MHz clock. ICR1 is set to 799, for a cycle time of 800 ticks, 50 us, 20 kHz. OCR1A sets the pulse width, and it's set at 400, at the middle of the PWM cycle. OCR1B determines the time that the ADC conversion is initiated, and it's varied from 100 ticks before the output switches to 100 ticks after. PWM output is on pin 9. For this test, pin 9 was connected directly to analog input A0. For each value of OCR1B, the sketch takes a number of analog readings, and reports the average value, maximum, and minimum of the analog readings.
#define NSAMPLES 256
volatile uint16_t analogs[NSAMPLES];
volatile uint16_t analogCtr = 0;
volatile uint16_t x;
void setup() {
analogRead(A0);
Serial.begin(115200);
Serial.println("OK");
pinMode(9, OUTPUT);
setupTimer1();
setupADC();
startTimer1();
for (uint16_t i = 300; i <= 500; i++) {
OCR1B = i;
delay(1);
startAcquisition();
while (analogCtr < NSAMPLES) {}
uint16_t avg;
uint16_t mx;
uint16_t mn;
avgMinMax(&avg, &mx, &mn);
Serial.print(i);
Serial.print('\t');
Serial.print(avg);
Serial.print('\t');
Serial.print(mx);
Serial.print('\t');
Serial.print(mn);
Serial.println();
Serial.flush();
}
}
void loop() {
}
void avgMinMax(uint16_t* avg, uint16_t* mx, uint16_t* mn) {
uint32_t sum = 0;
*mx = 0;
*mn = 0x3FF;
for (uint16_t i = 0; i < NSAMPLES; i++) {
sum += analogs[i];
if (analogs[i] < *mn) *mn = analogs[i];
if (analogs[i] > *mx) *mx = analogs[i];
}
*avg = (sum + (NSAMPLES >> 1)) / NSAMPLES;
}
ISR(ADC_vect) {
TIFR1 = (1 << OCF1B);
analogs[analogCtr++] = ADC;
if (analogCtr >= NSAMPLES) {
ADCSRA &= ~(1 << ADIE);
}
}
void startAcquisition() {
analogCtr = 0;
ADCSRA |= 1 << ADIE;
TIFR1 = (1 << OCF1B);
}
void setupADC() {
ADCSRA = 0;
DIDR0 = 1;
ADMUX = (1 << REFS0)|(0 << ADLAR)|(0 << MUX0);
// REFS1:0 - Reference -> AVcc
// MUX3:0 - Channel 0
ADCSRA = (1 << ADEN)|(0 << ADSC)|(1 << ADATE)|(1 << ADIF)|(0 << ADIE)|(4 << ADPS0);
// ADEN - Enable ADC
// ADSC - Don't start a conversion
// ADATE - Enable autotrigger
// ADIF - Clear pending ADC interrupt
// ADIE - Enable ADC interrupt
// ADPS2:0 - Prescaler; 4 -> 16 -> 1MHz -> 16 -> 13.5 us
ADCSRB = 5 << ADTS0;
// T1 OCR1B Compare Match Trigger
}
void setupTimer1() {
TIMSK1 = 0;
TCCR1B = 0; // Stop timer
TCCR1A = 0; // Normal mode.
ICR1 = 799; // 20 kHz at prescaler = 1
OCR1B = 400; // ADC trigger
OCR1A = 400; // Pulse width
TCCR1A = (2 << COM1A0) | 2 << WGM10; // OC1A Set @ BOTTOM, clear on match
TCCR1B = 3 << WGM12; // Mode = 14 - Fast PWM, TOP = ICR1
TIMSK1 = 0;//1<<TOIE1;
}
void startTimer1() {
TCCR1B |= (1 << CS10);
// CS12:0 - Prescaler select -> 1
}
Attached is a graph of the average value of the analog reading vs the time a conversion was initiated, relative to the time that the PWM output switches.
[Edit: spelling; attach graph]
