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