# 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;
}

void rising() {
attachInterrupt(0, falling, FALLING);
TCNT1 = 0;
//TCCR1B = (1 << CS11) | (1 << CS10);
TCCR1B = (1 << CS11);
//TCCR1B = (1 << CS10);
}

void falling() {
pwm_value = TCNT1;
TCNT1 = 0;
setOutput(pwm_value * 4);
attachInterrupt(0, rising, RISING);
}

ISR(TIMER1_OVF_vect) {      // interrupt overflow routine
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;
}

void rising() {
//setOutput(1000);
attachInterrupt(0, falling, FALLING);
TCNT1 = 0;
TCCR1B = (1 << CS11) | (1 << CS10);
//TCCR1B = (1 << CS11);
//TCCR1B = (1 << CS10);
}

void falling() {
pwm_value = TCNT1;
//TCNT1 = 0;
setOutput(pwm_value*8);
//setOutput(0);
attachInterrupt(0, rising, RISING);
}

ISR(TIMER1_OVF_vect) {      // interrupt overflow routine
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;
}

void rising() {
attachInterrupt(0, falling, FALLING);
TCNT1 = 0;
TCCR1B = (1 << CS11) | (1 << CS10);
//TCCR1B = (1 << CS11);
//TCCR1B = (1 << CS10);
}

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);
TCCR1B = 0;
TCNT1 = 0;
}

ISR(TIMER1_OVF_vect) {      // interrupt overflow routine
setOutput(0);
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;
}

void rising() {
attachInterrupt(0, falling, FALLING);
TCNT1 = 0;
//TCCR1B = (1 << CS11) | (1 << CS10);
TCCR1B = (1 << CS11);
//TCCR1B = (1 << CS10);
}

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);