I'm using the hardware timers on the 328 for phase angle control of a triac with zero-crossing detection. I am encountering some strange behavior with the "Set on Compare Match" feature not working as expected on Timer1. I am attempting to replicate the red "gate" trace here: https://playground.arduino.cc/Main/ACPhaseControl/, but with my own implementation in code.
Before building the triac and zero-cross detection circuit, I am using another timer (Timer0) to simulate a 120Hz AC square wave (2x the 60Hz frequency because the double crossing pulse will arrive twice a cycle).
The output of the synthesized AC square wave is fed into an external interrupt pin (INT0) with a jumper. The correct functionality of the program is a short pulse to fire the triac at some phase delay relative to the zero crossing interrupt event (another example: https://www.homemade-circuits.com/wp-content/uploads/2020/07/half-phase-control.jpg)
I configure Timer 1 in normal mode, and define compare levels for both OCR1A and OCR1B. OCR1A is the "phase delay" from zero crossing at which to fire the triac, and OCR1B sets the duration of the pulse used to fire the triac. In my code, Method 1 is working and producing the intended output, while Method 2 is not working (output is always ON). For brevity, I am highlighting the differences between the two methods below, and the rest of the code is the same.
Method 1:
- Set OC1A to do nothing on compare (normal port operation)
TCCR1A=0;
- "Manually" turn on the pin inside of the interrupt handler for output compare A. It will later be turned off in the interrupt handler for output compare B:
PORTB=(1<<PORTB1);
This produces the following (correct) output. In the oscilloscope screenshot, the yellow trace is the simulated AC square wave, and the green trace is to be the gate voltage on the triac. https://i.stack.imgur.com/tpSAw.png
Method 2:
- Enable the OC1A "Set at compare match" bit in the TCCR1A register:
TCCR1A |= (1 << COM1A1) | (1 << COM1A0);
- In theory, this means that I don't have to manually turn on the pin inside the interrupt handler for compare A, because it's already been turned on for us by the Compare Match Output Unit.
https://i.stack.imgur.com/45tFA.png
Note that in both methods, I still need to "manually" turn off the pin inside the output compare B interrupt handler because OC1A and OC1B are not the same physical pin: PORTB=0;
In Method 2, the pin is held ON, with no indication that the interrupt handler for output compare B is doing anything. My mental model of the port is that as long as the bit in the data direction register is set for that port, both the output compare unit and CPU can write to the pin. It appears that the pin is always being held ON, even after it is turned OFF by the CPU. Does the compare unit lock/latch the pin until the next clock cycle, or am I missing something else? Some tips here would be much appreciated.
I can prove that the output is indeed connected to OC1A because OC1A is PB1 which is connected to the oscilloscope and is being toggled in Method 1. I have vertically scaled OC1A (green) for readability against OC0A.
I have been referencing Section 13 of https://www.sparkfun.com/datasheets/Components/SMD/ATMega328.pdf for most of this task.
My code below for Method 1 is presented. To get Method 2, toggle the comments on the indicated lines.
#define F_CPU 16000000L
#include <avr/io.h>
#include <avr/interrupt.h>
/*
For reference:
OCR1A = turn_on_timer_level
OCR1B = turn_on_timer_level + pulse_width
*/
int timer0_ac_sim_val = 64; // Measured in clock ticks with prescale=1024
int turn_on_timer_level = 500; // Measured in clock ticks with prescale=64
int pulse_width = 200; // Measured in clock ticks with prescale=64
ISR(INT0_vect){
// ISR at output compare A of timer 0 turns on timer 1 - simulates what happens when a zero crossing rising edge is detected on a 120Hz AC sine wave
TCCR1B |= (1<<CS11) | (1<<CS10);
TCNT1 = 0;
}
ISR(TIMER1_COMPA_vect){
// ISR at output compare A of timer 1 turns on the pin
PORTB = (1<<PORTB1); // Turn on the pin COMMENT FOR METHOD 2
}
ISR(TIMER1_COMPB_vect){
TCCR1B = 0; // Turn off the timer
PORTB = 0; // Turn off the pin
}
int main(){
// Timer 0 setup for a 120Hz square wave on OC0A
DDRD |= (1<<PD6);
TCCR0A = 0x0;
TCCR0A |= (1<<COM0A0) | (1<<WGM01); // Set the output to toggle on compare
TCCR0B = 0x0;
TCCR0B |= (1<<CS02) | (1<<CS00); // Configure with a prescale of 1024 and start clock
OCR0A = timer0_ac_sim_val; // Calculate this value for a 120Hz square wave, doesn't change.
// Enable external interrupt 0 to trigger on a rising edge - set the External Interrupt Control Register
EICRA |= (1<<ISC01) | (1<<ISC00);
EIMSK |= (1<<INT0);
// Global interrupt enable
sei();
// Timer 1 setup
DDRB |= (1<<PORTB1);
TCCR1A = 0x0; // No pin action on output compare
// TCCR1A |= (1 << COM1A1) | (1 << COM1A0); // Set OC1A on compare match UNCOMMENT FOR METHOD 2
TCCR1B = 0x0; // Disable clock
OCR1A = turn_on_timer_level; // Delay between OC0A rise and OCR1A trigger
OCR1B = turn_on_timer_level + pulse_width; // Delay between OC0A rise and OCR1B trigger
// Enable timer 1 interrupts
TIMSK1 |= (1<<OCIE1A) | (1<<OCIE1B);
while(1){
};
}