Measure PWM: 128us period properly?

Hey guys,

I have a very simple problem to solve but my knowledge about timers on arduino is limited and - honestly, they were always terrible to use in the first place. I hope someone can help and solve my problem.

Goal:

  • Measure an 8bit PWM signal coming from one Arduino (both Arduinos running at 16mhz, 1/8 prescaler) with a period time of 128us.
  • Convert to 10bit DAC based on resistor ledder (the R2R ledder is working fine when using fixed values)
  • If PWM drops out, or enable bit goes low, kill R2R DAC value to 0

Very simple, that’s all.

Here is my code:

/*
  PWM2DAC
  Tool to convert PWM from  laser cutter into a 10 bit DAC in realtime as fast as possible.

  Author: Timo Birnschein
  email: timo.birnschein@microforge.de
*/

  
volatile uint16_t pwm_value = 0;
const byte interruptPin = 2;
  
// the setup function runs once when you press reset or power the board
void setup() 
{
  DDRB = 0x1F; // First five bits output
  DDRC = 0x1F; // First five bits output
  DDRD = 0x00; // All inputs

  PORTB = 0x00;
  PORTC = 0x00;
  
  // enable Timer0 overflow interrupt:
  bitSet(TIMSK1, TOIE1);

  // when pin D2 goes high, call the rising function  
  attachInterrupt(digitalPinToInterrupt(interruptPin), rising, RISING);

  TCNT1 = 0;
  TCCR1B = (1 << CS11);
}

// the loop function runs over and over again forever
void loop() 
{
  
}

void setOutput(uint16_t value)
{
  PORTB = value & 0x1F;
  PORTC = (value >> 5) & 0x1F;
}

// Runs about 3.2 uS after rising edge (about 50 instructions)
void rising() {
  attachInterrupt(0, falling, FALLING);
  TCNT1 = 0;
  //TCCR1B = (1 << CS11) | (1 << CS10);
  TCCR1B = (1 << CS11);
  //TCCR1B = (1 << CS10);
}

// Runs about 3.2 uS after falling edge (about 50 instructons)
void falling() {
  pwm_value = TCNT1;
  TCNT1 = 0;
  setOutput(pwm_value * 4);
  attachInterrupt(0, rising, RISING);
}

ISR(TIMER1_OVF_vect) {      // interrupt overflow routine
  // preload timer
  TCNT1 = 0;
  TCCR1B = 0;
  setOutput(0);
}

Problems

  • It takes my ISR 3.2 microseconds to do something! Sounds like a lot to me but I might be mistaken.
  • Even though the measuring arduino is doing nothing else, there is a lot of jitter in the measurement and can be regarded as unusable.
  • The values even change within a period as if the ISR get executed twice and I don’t see why that should be the case.
  • Killing the R2R to 0 using the timer overflow ISR simply doesn’t do anything. It seems the ISR never fires.
  • Under certain condidisions I see multiple DAC value changes per PWM period!

I sincerely hope, someone can help me with this. I spend the better part of yesterday trying to figure this out but I had zero success.

Oh, one remark: I know that INT0 is not an ideal way to measure this. I should use the Input Capture Pin instead. Unfortunately, I mapped my LSB of my R2R ledder to that pin. However, I’m willing to use only 9 bits and rework the PCB if you can offer a code that will work properly on the ICP.

Thank you very much!
Timo

Ok, I just had some success. I changed the PWM prescaler to 1/64 and the PWM to DAC conversion seems to be relatively stable now. I have one last issue, I need to be able to shut it down by using the overflow interrupt but that just won’t fire for whatever reason…

/*
  PWM2DAC
  Tool to convert PWM from  laser cutter into a 10 bit DAC in realtime as fast as possible.

  Author: Timo Birnschein
  email: timo.birnschein@microforge.de
*/

  
volatile uint16_t pwm_value = 0;
const byte interruptPin = 2;
  
// the setup function runs once when you press reset or power the board
void setup() 
{
  DDRB = 0x1F; // First five bits output
  DDRC = 0x1F; // First five bits output
  DDRD = 0x00; // All inputs

  PORTB = 0x00;
  PORTC = 0x00;
  
  // enable Timer0 overflow interrupt:
  //bitSet(TIMSK1, TOIE1);

  // when pin D2 goes high, call the rising function  
  attachInterrupt(digitalPinToInterrupt(interruptPin), rising, RISING);

  pwm_value = 0;
}

// the loop function runs over and over again forever
void loop() 
{
  
}

void setOutput(uint16_t value)
{
  PORTB = value & 0x1F;
  PORTC = (value >> 5) & 0x1F;
}

// Runs about 3.2 uS after rising edge (about 50 instructions)
void rising() {
  //setOutput(1000);
  attachInterrupt(0, falling, FALLING);
  TCNT1 = 0;
  TCCR1B = (1 << CS11) | (1 << CS10);
  //TCCR1B = (1 << CS11);
  //TCCR1B = (1 << CS10);
}

// Runs about 3.2 uS after falling edge (about 50 instructons)
void falling() {
  pwm_value = TCNT1;
  //TCNT1 = 0;
  setOutput(pwm_value*8);
  //setOutput(0);
  attachInterrupt(0, rising, RISING);
}

ISR(TIMER1_OVF_vect) {      // interrupt overflow routine
  // preload timer
  TCNT1 = 0;
  TCCR1B = 0;
  setOutput(0);
}[\code]

This seems to finally work…

/*
  PWM2DAC
  Tool to convert PWM from  laser cutter into a 10 bit DAC in realtime as fast as possible.

  Author: Timo Birnschein
  email: timo.birnschein@microforge.de
*/

  
volatile uint16_t pwm_value = 0;
const byte interruptPin = 2;
volatile uint16_t testint= 0;
  
// the setup function runs once when you press reset or power the board
void setup() 
{
  DDRB = 0x1F; // First five bits output
  DDRC = 0x1F; // First five bits output
  DDRD = 0x00; // All inputs

  PORTB = 0x00;
  PORTC = 0x00;
  
  // enable Timer0 overflow interrupt:
  bitSet(TIMSK1, TOIE1);

  // when pin D2 goes high, call the rising function  
  attachInterrupt(digitalPinToInterrupt(interruptPin), rising, RISING);

  pwm_value = 0;

//##########################################
// setting up the timer:
TCCR1A = 0; // Reset control registers timer1 /not needed, safety
TCCR1B = 0; // Reset control registers timer1 // not needed, safety
TIMSK1 = B00000011; //timer1 output compare match and overflow interrupt enable
OCR1A = 130; // Set TOP/compared value (your value) (maximum is 65535 i think)
TCCR1B = (1 << CS11) | (1 << CS10);  // prescaling=64 CTC-mode (two counts per microsecond)
//###################################################



  
}

// the loop function runs over and over again forever
void loop() 
{
  
}

void setOutput(uint16_t value)
{
  PORTB = value & 0x1F;
  PORTC = (value >> 5) & 0x1F;
}

// Runs about 3.2 uS after rising edge (about 50 instructions)
void rising() {
  attachInterrupt(0, falling, FALLING);
  TCNT1 = 0;
  TCCR1B = (1 << CS11) | (1 << CS10);
  //TCCR1B = (1 << CS11);
  //TCCR1B = (1 << CS10);
}

// Runs about 3.2 uS after falling edge (about 50 instructons)
void falling() {
  pwm_value = TCNT1;
  setOutput(pwm_value * 8);
  TCNT1 = 0;
  TCCR1B = (1 << CS11) | (1 << CS10);
  //TCCR1B = 0;
  attachInterrupt(0, rising, RISING);
}

ISR(TIMER1_COMPA_vect)
{
  setOutput(0);
  // preload timer
  TCCR1B = 0;
  TCNT1 = 0;
}

ISR(TIMER1_OVF_vect) {      // interrupt overflow routine
  setOutput(0);
  // preload timer
  TCCR1B = 0;
  TCNT1 = 0;
}

But I have some flickering left. I will try to measure the 8 bit PWM with the 16 bit timer at a higher resolution using a different prescaler. That should give me less jitter - hopefully…

Final code that seems to work for 500us signals. I get at about 1000 ticks which seems sufficient for an 8bit signal:

/*
  PWM2DAC
  Tool to convert PWM from  laser cutter into a 10 bit DAC in realtime as fast as possible.

  Author: Timo Birnschein
  email: timo.birnschein@microforge.de
*/

  
volatile uint16_t pwm_value = 0;
const byte interruptPin = 2;
volatile uint16_t testint= 0;
  
// the setup function runs once when you press reset or power the board
void setup() 
{
  DDRB = 0x1F; // First five bits output
  DDRC = 0x1F; // First five bits output
  DDRD = 0x00; // All inputs

  PORTB = 0x00;
  PORTC = 0x00;
  
  // enable Timer0 overflow interrupt:
  bitSet(TIMSK1, TOIE1);

  // when pin D2 goes high, call the rising function  
  attachInterrupt(digitalPinToInterrupt(interruptPin), rising, RISING);

  pwm_value = 0;

  //##########################################
  // setting up the timer:
  TCCR1A = 0; // Reset control registers timer1 // not needed, safety -- TIMO: I doubt it's not needed. Because without it didn't work!
  TCCR1B = 0; // Reset control registers timer1 // not needed, safety
  TIMSK1 = B00000011; //timer1 output compare match and overflow interrupt enable
  OCR1A = 1200; // Set TOP/compared value (your value) (maximum is 65535 i think)
  //TCCR1B = (1 << CS11) | (1 << CS10);
  TCCR1B = (1 << CS11);  // prescaling=64 CTC-mode (two counts per microsecond)
  //###################################################



  
}

// the loop function runs over and over again forever
void loop() 
{
  
}

void setOutput(uint16_t value)
{
  if (value > 1023) value = 1023;
  PORTB = value & 0x1F;
  PORTC = (value >> 5) & 0x1F;
}

// Runs about 3.2 uS after rising edge (about 50 instructions)
void rising() {
  attachInterrupt(0, falling, FALLING);
  TCNT1 = 0;
  //TCCR1B = (1 << CS11) | (1 << CS10);
  TCCR1B = (1 << CS11);
  //TCCR1B = (1 << CS10);
}

// Runs about 3.2 uS after falling edge (about 50 instructons)
void falling() {
  pwm_value = TCNT1;
  setOutput(pwm_value * 1.063);
  //setOutput(1023);
  TCNT1 = 0;
  //TCCR1B = (1 << CS11) | (1 << CS10);
  TCCR1B = (1 << CS11);
  //TCCR1B = 0;
  attachInterrupt(0, rising, RISING);
}

ISR(TIMER1_COMPA_vect)
{
  setOutput(0);
  // preload timer
  TCCR1B = 0;
  TCNT1 = 0;
}

ISR(TIMER1_OVF_vect) {      // interrupt overflow routine
  setOutput(0);
  // preload timer
  TCCR1B = 0;
  TCNT1 = 0;
}