ADC controlled duty cycle of a PWM ouput

Dear all,

i’ve working on this project for some time using Nick Gammon’s forum hints (great stuff!) and some AVRfreaks threads, but still have issues and would need some guidance here.

I’d like to investigate a pulsed arc with a high velocity camera. Due to the variable light intensity the dynamic range of the camera is insufficient to correctly expose the high and low phase of the pulse, hence i’d like to make the exposure time variable while recording. It’s possible as the camera can be controlled by a PWM signal with variable duty cycle. So i thought i use a simple photodiode connected to the ADC of an Arduino Mega2560 board.

Functional principle:
The frequency of a PWM output (i.e. frame rate - from 1.000 up to 25.000 fps), as well as the amount of pulses (i.e. pictures) is being set in the beginning and stays constant during the recording. The duty cycle of the output signal (i.e. exposure time - between 5-95% of the frame rate period) is being varied according to the actual light intensity.

My assumptions so far:

  • Timer1 in phase & frequency correct mode for PWM generation
  • Pulse counting in Timer1 overflow interrupt handler
  • fast ADC conversion (sampling rate of 1 MHz)

So far so good - i can output a defined amount of pulses with a constant frequency and a varying duty cycle…

Issues:

  • output changes logic - for a low light intensity the output is high and goes low for a pulse and vice versa, for a high light intensity the output is low and goes high for a pulse
  • duty cycle seems to be constant for the first 2 pulses and afterwards varies as it’s supposed to do…

I think it has to do with the update of the OCR1A register of the timer, but was unable to figure it out yet. Would it be better to run the ADC in free running mode to cure the constant duty cycle problem in the beginning? Also, i don’t have a clue why the output changes the logic.

Thanks for your help,
mike

Code:

#include <math.h>

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define INLENGTH 7                           // Maximal length of the string of numbers, representing the frequency.
#define INTERMINATOR 'H'                     // 'H' of 'Hz'
#define MinFreq 2                            // Lowest frequency possible.
#define MaxFreq 8000000                      // Highest frequency possible.
#define DutyMin 0.05                         // Min duty cycle of HV-cam 
#define DutyMax 0.95                         // Max duty cycle of HV-cam
#define FASTADC 1                            // AnalogIN 1 (PF1)            

char inString[INLENGTH+2];                   // Array, which contains the frequency, plus 'Hz'.
int inCount;                                 // Counter.
int N;                                       // Prescaler.
volatile int cycleLength;                    // Amount of counting steps.
float frequency;                             // Frequency.
float current_diode;                         // Value measured by analogRead. 
volatile float current_rel;                  // Noramlized values of current_diode.
volatile int newOCR;
int frames;
volatile int i=0;
bool enable=0;
int timer_start = 0;

const unsigned int currentMin = round(DutyMin*1024);
const unsigned int currentMax = round(DutyMax*1024);


//---------------------------- ISR ----------------------------------

ISR(TIMER1_OVF_vect) {         
//  OCR1A = newOCR;
  i++;
}

//---------------------------- Setup -------------------------------------
void setup() {
  Serial.begin(9600);
  
  #if FASTADC
      ADCSRA |= ( (0<<ADPS2) );                    // Setting prescaler of the ADC to 16, resulting a sampling rate of 1 MHz.     
      ADCSRA &= ~( (1<<ADPS1) | (1<<ADPS0) );              
  #endif 
  
  sbi(DDRB, DDB5);                                 // OC1A
  pinMode(PB5, OUTPUT);
 
  cbi(TCCR1A, WGM11);                              // Use phase & frequency correct pwm mode, WGM13:0 = 8,
  cbi(TCCR1A, WGM10);
  cbi(TCCR1B, WGM12);
  sbi(TCCR1B, WGM13);
  
  sbi(TCCR1A,COM1A1);                              //set OC1A on Compare Match when up-counting
  sbi(TCCR1A,COM1A0);
  
  TCNT1 = 0;                                       // Resetting timer 1.
  
  Serial.println("Bitte Anzahl der Frames: ");
  frame_count();
  
  Serial.println("Bitte Frequenz eingeben (mit Hz)");
  frequency_fct();
  
  if(frequency >= 244)                                    
    N=1;                                                                                                         
  if(frequency >= 30 && frequency < 244)                  
    N=8;                                                                                                        
  if(frequency >= 3 && frequency < 30)                    
    N=64;                                                                                                      
  if(frequency >= 1 && frequency < 3)
    N=256;
  if(frequency < 1)
    N=1024;
    
  cycleLength = (16000000 / (2 * N * frequency));    // Calculating the amount of counting steps necessary,  
  Serial.print("Timer count: ");                     //considering the chosen frequency and corresponding prescaler.
  Serial.println(cycleLength,DEC);
  Serial.print("Prescaler: ");
  Serial.println(N,DEC);

  OCR1A = round(cycleLength/2);                      // Initial duty cycle: 50%
  ICR1 = cycleLength;
 
  // Setting prescaler bits.
  switch (N){                                              
    case 1:    TCCR1B |= ( (1<<CS10) );                  
               TCCR1B &= ~( (1<<CS12) | (1<<CS11) );
               break;
    case 8:    TCCR1B |= ( (1<<CS11) );
               TCCR1B &= ~( (1<<CS12) | (1<<CS10) );
               break;
    case 64:   TCCR1B |= ( (1<<CS11) | (1<<CS10) );
               TCCR1B &= ~( (1<<CS12) );
               break;
    case 256:  TCCR1B |= ( (1<<CS12) );
               TCCR1B &= ~( (1<<CS11)| (1<<CS10) );
               break;
    case 1024: TCCR1B |= ( (1<<CS12) | (1<<CS10) );
               TCCR1B &= ~( (1<<CS11) );
               break;
  }
  
  timer_start = TCCR1B;

  cbi(TCCR1B,CS10);                                  // Timer 1 stop
  cbi(TCCR1B,CS11);
  cbi(TCCR1B,CS12);

  sbi(TIMSK1,TOIE1);
  sei();                                             // Enable all interrupts.
}

//--------------------- Loop ---------------------------------------
void loop(){
  if(enable==1)  {
    if(i<frames)  {
      InputDiode();
      newOCR = round(cycleLength*(1-current_rel/1023));
      OCR1A = newOCR;
      TCCR1B = timer_start;
    }
    else {
      TCCR1B = 0;
      enable = 0;
      i=0;
      Serial.println("Done!");
    }
  }
  else {
    Serial.print("Aufnahme [1: GO!]: ");
    enable_fct();
    }
}

//--------------------- Subroutines -------------------------

void InputDiode(){ 
  current_diode = analogRead(PF1); 
  current_rel = constrain( current_diode, currentMin, currentMax);   
}

//------------------------
void frequency_fct()  {  
  frequency_input();
  frequency=atol(inString);                              // Converting the array into a string of numbers. 
  frequency=constrain(frequency, MinFreq, MaxFreq);      // Checking if the frequency is part of the value range.      

  Serial.print("Es wird gesetzt: ");
  Serial.print(frequency,DEC);
  Serial.println("Hz!");
  Serial.println("--------------------------------------");
  Serial.println("Bitte Serial Monitor neu starten, um eine andere Frequenz einzugeben.");
}

//------------------------
void frame_count() {
  frequency_input();
  frames=atol(inString);
  
  Serial.print("Es werden: ");
  Serial.print(frames,DEC);
  Serial.println(" Frames aufgenommen!");
}

//------------------------
void enable_fct() {
  frequency_input();
  enable=atol(inString);
  
  Serial.println("Go baby, go!!");
}

//------------------------- Function which generates the array, used above to receive the frequency.
void frequency_input(){                                         
  inCount=0;                                                    
  do 
  {
   while (Serial.available() == 0);                             
    inString[inCount] = Serial.read();                          
    if(inString[inCount] == INTERMINATOR) break;
    if( (inString[inCount]<'0') || (inString[inCount]>'9') ){   
      inCount--;
    }
  }
  while(++inCount < (INLENGTH+1));                              
  Serial.flush();
}

I think you're confused. The ADC pins and PWM pins are not the same . ADC is an acronym for Analog to Digital which converts an analog voltage to a digitial value (0 to 1023). PWM pins are marked with a tilde ~ symbol and output a variable duty cycle from 0 to 255 (0 to 100%). Refer to the pinout of the 2560 to identify the PWM pins. (not the analog pins)

raschemmel, thanks for your answer.

I’m aware of what you’re saying - my diode is connected to the Analog In 1 pin and my output is the PWM 11 (aka PB5) pin. I do a conversion of the ADC values (0-1023) to “timer”-values (0-65535 for timer1) as well, see code:

newOCR = round(cycleLength*(1-current_rel/1023));

where cycleLength is the timer counter value needed to reach the frequency.

That is similar to what the MAP function does.: http://arduino.cc/en/reference/map

I don't see your definition for PF1.

  current_diode = analogRead(PF1);

GMAW-Mike: Functional principle: The frequency of a PWM output (i.e. frame rate - from 1.000 up to 25.000 fps), as well as the amount of pulses (i.e. pictures) is being set in the beginning and stays constant during the recording. The duty cycle of the output signal (i.e. exposure time - between 5-95% of the frame rate period) is being varied according to the actual light intensity.

My assumptions so far: - Timer1 in phase & frequency correct mode for PWM generation - Pulse counting in Timer1 overflow interrupt handler - fast ADC conversion (sampling rate of 1 MHz)

So far so good - i can output a defined amount of pulses with a constant frequency and a varying duty cycle...

Issues: - output changes logic - for a low light intensity the output is high and goes low for a pulse and vice versa, for a high light intensity the output is low and goes high for a pulse - duty cycle seems to be constant for the first 2 pulses and afterwards varies as it's supposed to do...

That sort of code is very difficult to figure out without being able to try it and view the results on an oscilliscope.

I don't understand the sentence in bold at all. It sounds to me like a rough explanation of the normal behaviour of varying PWM.

It would certainly help me if you describe what is happening in more detail.

This is what i think is supposed to happen (please correct all errors)

The Arduino generates a PWM signal at a predefined frequency (say 1000 Hz for one run of the camera, 25,000 for another) The camera frame rate is dictated by the PWM fequency The camera takes no notice of the PWM duty cycle The PWM duty cycle is used to switch on a bright light for variable periods in sync with the camera frames The Arduino uses fast ADC to measure the light intensity as often as possible As the measured light intensity varies the Arduino will alter the PWM duty cycle to compensate

If the sentence in bold means that the phasing of the PWM signal changes I would be suspicious that changing the PWM duty cycle causes the timer to restart so that, perhaps, 2 HIGH sequences follow each other rather than HIGH LOW HIGH LOW

I'm wondering if it is necessary to use the Timer to produce the varying pulse width - i.e. perhaps the in-built PWM is not appropriate. Perhaps the Timer could be used to trigger the start of each pulse and "regular code" (Blink Without Delay using micros()) would determine the rest of the sequence until the next Timer interrupt. That way there would be no need to fiddle with the Timer once it is set for a run. If "regular code" would not be precise enough another thought is to use a second timer to produce the pulse width as one-shot events.

...R

Hi,

sorry for the belayed answer - i took the weekend off to recuperate a bit…

raschemmel, didn’t know such a function existed. Thanks!

Robin, it’s not exactly how the system is supposed to work but you’re quiet close. I prepared some graphs to better depict the system as well as the changing logic problem.

The whole system contains a photodiode with a resistor and the Arduino board (see graph). As the welding process emits enough light, i do not use any additional lights. The board produces just one signal which is controlling the camera. The camera is recording as long as the input signal is high.

What is supposed to happen - please compare with the second graph:

  • my welding process is running and i decide to start a recording by setting the enable signal to 1 (via serial monitor)
  • the Arduino generates just one PWM signal with a predefined frequency which is the frame rate of the camera
  • the camera do take notice of the duty cycle - the duty cycle length is the exposure time for each frame.
  • the Arduino uses fast ADC to measure the light intensity as often as possible
  • As the measured light intensity varies the Arduino will alter the PWM duty cycle to compensate
  • the amount of pulses (i.e. frames to be recorded) is also predefined before setting the enable signal

To explain the output changing problems i made an example using a flashlight in 3 different positions to simulate the arc - please see the third graph. The Arduino is supposed to send 5 pulses with 1 kHz. At first, the torch is off hence “min intensity”. Then the flashlight is turned on (“middle intensity”) and finally is directly in front of the photodiode (“max intensity”).
What happens:

  • if the light intensity is high or somewhere in between, everything works properly (remember - high signal is recording)
  • if the light intensity is low the output signal changes the logic - the pulses seem to be correct but the output stays high afterwards. Since the camera is recording while high, it begins to produce garbage…

I hope this will help you to understand the whole system.

OK. It took me a while to spot the difference between your description and mine.

I think you are using the Arduino to control the exposure time for each frame rather than the intensity of the light.

I found this line confusing

The camera is recording as long as the input signal is high.

because I thought you meant it would run for (say) 5 minutes while the line was high for 5 minutes - but I think you mean that it "keeps the shutter open" during the HIGH part of the PWM cycle.

I'm not sure how to interpret your graphs. They look like the normal progression from a max to a min duty cycle. Obviously for controlling the speed of a motor or the brightness of an LED the exact location of the HIGH would not matter. But I'm not clear why it matters in your case.

And if it does matter I fall back to my earlier comment about replacing PWM with the use of the Timer to start the pulses at precise intervals and a separate mechanism to stop them after the required interval.

...R

Exactly. One can say i'm using the Ardu to control the exposure time based on the light intensity of the process.

And yes, as long as the trigger signal is high the shutter stays open. Of course if the signal is permanent high the camera begins to do something to protect the internals or so, but not that what it was supposed to do... And that's the case if the measured light intensity is low - the output signal stays high instead going low after the last pulse, so the camera stays open and produces some artefacts or plainly rubbish.

Simultaneously to the camera i'm measuring the arc voltage and current with a scope, hence the Arduino triggering should be reliable from frame to frame so that i can synchonize it afterwards.

GMAW-Mike: Exactly. One can say i'm using ....... SNIP

You did not respond to the question and comments in my last 2 paragraphs.

...R

Sorry mate! I somehow overlooked them!

I’m not sure how to interpret your graphs. They look like the normal progression from a max to a min duty cycle. Obviously for controlling the speed of a motor or the brightness of an LED the exact location of the HIGH would not matter. But I’m not clear why it matters in your case.

Hm… I think it does matter. Take the LED as an example - if the PWM output stays HIGH after the predefined amount of pulses as depicted on the graph, than it would just stay on?!
I’ve just checked the camera’s manual once again and it reacts on the rising and falling edges, so the camera is waiting for a falling edge after recognising a rising one to finish the exposition. Hence a HIGH state after a series of pulses as shown on the graph, could be a source of error. Unfortunately, i’m unable to try it out because the camera is in action now. I’ll get 'er back on wednesday, so i can tell you more then.
Nevertheless, is there any possibility to define the state to be HIGH or LOW after a series of pulses using this type of PWM (phase and frequency correct)? Both bits COM1A1:0 in TCCR1B are set to 1, so the output is set when upcounting and cleared when downcounting…

And if it does matter I fall back to my earlier comment about replacing PWM with the use of the Timer to start the pulses at precise intervals and a separate mechanism to stop them after the required interval.

You mean sth like this? (This may not work as i just hammered it down right now without uploading it to the Duino…)

ISR(TIMER1_COMPA_vect)
{
  cbi(PORTB,PB5);                          // Clear PIN 11
  TCCR1B = 0x18;                              // Stop counting 
  TCNT1 = 0;                                  // Set Timer Counter to zero
  OCR1B = newOCR;
  i++;
}

ISR(TIMER1_COMPB_vect)
{ 
  sbi(PORTB,PB5);                          // Set PIN 11 to HIGH
}

void loop(){
  if(enable==1)  {
    if(i<frames)  {
      InputDiode();
      newOCR = round(cycleLength*(1-current_rel/1023));     //replace with map();
      TCCR1B = timer_start;
       }
    else {
      TCCR1B = 0;
      enable = 0;
      i=0;
      Serial.println("Done!");
    }
  }
  else {
    Serial.print("Aufnahme [1: GO!]: ");
    enable_fct();
    }
}

I used a sth like this once in a previous project… What type of timer operation mode should be used here? Back then i used FastPWM, but in this case the normal mode of operation (i.e. WGM13:0 all cleared) should work, shouldn’t it?

GMAW-Mike: if the PWM output stays HIGH after the predefined amount of pulses

Ahhh ... I had not realized the problem arises at the end of the PWM period. I thought it was arising during the period -so my earlier suggestion is probably irrelevant.

In that case, why not just use digitalWrite() to force the pin LOW at the end of the PWM period ?

...R

The digitalWrite() has been ignored regardless where i positioned it… I then realised, that i’m starting the timer every time i’m in the loop and iframes. I tried to stop the timer in the ISR handler and it cured the problem - the logic is as it should be, the output is LOW at the end of the PWM period. Success, i thought…

Unfortunately, the frequency is now incorrect - when 1 kHz is set, the output has a frequency of 900 Hz. The difference is getting bigger with higher frequencies, for ex. if 25 kHz are set the output has about 21.8 kHz… I just added TCCR1B = 0; in the timer overflow interrupt handler, that’s all. I thought it’s connected with the ADC conversion time, but when i turned the ADC conversion off the difference is still there. It got a bit smaller though. No idea what’s going on… It seems that some kind of magic is happening inside of my Arduino.

This week was a bit stressful workwise, so I’ll look into it the code during the weekend and probably try realize a “manual” PWM with setting and clearing the output pin in interrupt handlers on compare match…