Replacing Timer1 ISRs by controlling the OC1A pin directly

Go To Last Post
30 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi folks,

 

I am looking for help to improve this zero-cross detecting / triac triggering code that I've implemented on an ATtiny85. It is working without problems on a 220V / 50Hz mains, but I would like to get rid, if possible (I suspect it is, but I can not make it work), of the two timer ISRs I'm using at the moment.

 

Now it works this way: the ZCD pulses present in PCINT3 activate the pin change ISR (I can not use INT0 because I am using I2C). This ISR resets the Timer1 counter, configured in CTC mode, and puts it to count. Upon reaching the selected OCR1C value, the compare match ISR is activated, where PB1, which controls the triac gate, is put on high. This same ISR sets the Timer1 counter to overflow 3 or 4 ticks later, managing the duration of the triac start pulse.

 

Finally, the Timer1 overflow ISR puts in low PB1 and turns off the triac.

 

Would it be possible to achieve the same operation by making timer1 activate/deactivate OC1A? Thus discarding the Timer interrupts that I am using?

 

Thank you very much in advance!

 

// Includes ...

// Defines
#define PWR_CTRL_PIN 	PB1
#define PWR_CTRL_DDR	DDRB
#define PWR_CTRL_PORT	PORTB
#define ZCD_PIN		PB3
#define ZCD_DDR		DDRB
#define ZCD_PORT	PORTB
#define ZCD_INP		PINB
#define CHANGETIME	0xFFFF
#define MINGATEDELAY 	7	/* OK @50Hz 7 tics after ZCD */
#define MAXGATEDELAY 	147	/* OK @50Hz 147 tics are the maximum safe triac switching resolution */
#define PULSELENGTH 	3	/* OK @50Hz 3 tics are enough to fire the triac */

// Global variables
volatile uint16_t ocrChangeDelay = 0;
volatile uint8_t newValueFromI2C = UsiTwiReceiveByte();

// Function prototypes ...

// Main
int main(void) {
	// Setup
	Setup();
	// Loop
	Loop();
	return 0;
}

// Pin Change ISR (Zero Cross Detection)
ISR(PCINT0_vect) {
	if(ZCD_INP & (1 << ZCD_PIN)) {			/* If it was triggered by a rising edge, run ISR code */
		TCNT1 = 0;				/* Reset Timer1 counter */
		TCCR1 |= ((1 << CS13) | (1 << CS11));	/* Start Timer1 with prescaler 512 (I/O clock / 512) */
	}
}

// Timer1 Compare Match ISR (Triac switch on after counter compare match *fire delay*)
ISR(TIMER1_COMPA_vect) {
	PWR_CTRL_PORT |= (1 << PWR_CTRL_PIN);		/* Turn Triac On */
	TCNT1 = (255 - PULSELENGTH);			/* Set Timer1 counter for overflow interrupt */
}

// Timer1 Overflow ISR (Triac switch off after counter overflow *pulse length*)
ISR(TIMER1_OVF_vect) {
	PWR_CTRL_PORT &= ~(1 << PWR_CTRL_PIN);		                        /* Turn Triac Off */
	TCCR1 &= ~((1 << CS13) | (1 << CS12) | (1 << CS11) | (1 << CS10));	/* Set Timer1 off - Prescaler 0 */
}

// Setup
void Setup(void) {
	clock_prescale_set(8);				/* Set CPU @1 MHz (System clock / 8) */
	PWR_CTRL_DDR |= (1 << PWR_CTRL_PIN);      	/* Set led pin Data Direction Register for output */
	PWR_CTRL_PORT &= ~(1 << PWR_CTRL_PIN);		/* Turn Triac Off */
	_delay_ms(500);					/* Delay to allow ISP programming at 1 MHz after power on */
	clock_prescale_set(1);				/* Set CPU @8 MHz (System clock / 1) for safe I2C slave operation */
	cli();						/* Disable interrupts */
	SetPinChangeInterrupt();			/* Enable pin change interrupt for zero cross detection */
	SetTimer1Interrupt();				/* Enable timer1 interrupts for modulating phase angle */
	sei();						/* Enable interrupts */
}

// Main Loop
void Loop (void) {
	for(;;) {
		OCR1C = newValueFromI2C;
	}
}

// Function SetPinChangeInterrupt
void SetPinChangeInterrupt(void) {
	ZCD_DDR &= ~(1 << ZCD_PIN);			/* Set PB3 for input to detect the Zero Cross pulse */
	ZCD_PORT &= ~(1 << ZCD_PIN);			/* Disable PB3 pull-up resistor (high-impedance state) */
	GIMSK |= (1 << PCIE);				/* Enable Pin Change Interrupt */
	PCMSK |= (1 << PCINT3);				/* Set Pin Change Mask Register to allow ZCD on PB3 */
}

// Function SetTimer1Interrupt
void SetTimer1Interrupt(void) {
	TCCR1 |= (1 << CTC1);				/* Set Timer1 CTC mode */
	TIMSK |= ((1 << OCIE1A) | (1 << TOIE1));	/* Enable Timer1 compare match and overflow interrupts */
	OCR1C = MINGATEDELAY;				/* Set Timer1 comparator to "MINGATEDELAY" */
}

 

 

 

 

Attachment(s): 

This topic has a solution.
Last Edited: Fri. Nov 10, 2017 - 02:41 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It would be helpful to know the purpose of mounting this snip-hunting expedition.

 

If it works, what is the impetus to change?  Are ISRs frowned upon by the elders?

 

Is this "full Monte" -- err, "full Arduino"?  Or just a vanilla C app written in Arduino style?

 

casanovg wrote:
(I can not use INT0 because I am using I2C).

How many metaphors are mixed in that statement?  Now I have to dig out a representative datasheet...how many interrupt sources are there for that model?  How many coupld be put into play?  As this model has no inherent I2C, what does it matter?

 

A couple ISRs might make sense to me.  After all, mains work is slow, right?  Many milliseconds between events.

 

One could employ, besides e.g INT0, analog comparator or T0 which also overlaps INT0.  No ICP on this model.  Depending on the reach of the numbers from the zero crossing and accuracy needs,  perhaps there are 'single hit" solutions.  More numbers are needed.

 

 

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Wed. Nov 8, 2017 - 10:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't think that using the hardware compare will eliminate your timer isrs - you still need to load the next event once you get a match. The only advantage I can see is that you might eliminate the isr latency when activating the port pin.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You could program the timer in PWM mode with the intended low and high times.

Output the PWM wave to PB1.

 

Then, the pin change interrupt only needs to start the timer, while the overflow stops it (via prescaler).

 

PWM mode auto-resets the timer, so this method has less interrupts and they do a bit less work.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi theusch, thanks for your answer. Regarding your comments:

It would be helpful to know the purpose of mounting this snip-hunting expedition.

If it works, what is the impetus to change?  Are ISRs frowned upon by the elders?

Well, my goal is to get cleaner, less intricate code and, as Kartman mentions, eliminate the latency of 2 ISRs, and maybe save some bytes in flash memory. One last point is to learn something new.

Is this "Full Mount" - err, "full Arduino"? Or simply a Vanilla C application written in Arduino style?

Yes, it's plain C, maybe written Arduino style :)

How many sources of interruption are there for that model? How many could be put in play?

I have not described the entire application to keep the focus on eliminating the Timer1 ISRs, but the tiny85 pins that I am using at this moment are: 5 (I2C SDA), 7 (I2C SCL), 6 (Triac gate control output), 3 (ADC2 to calculate current through a hall effect sensor), 2 (Zero cross pulse input). This AVR is a kind of "AC handling I2C device" working as a slave to another MCU.

A pair of ISR could make sense to me. After all, network work is slow, right? Many milliseconds between events.

That's true, in fact, 10 ms between PCINT3 ISR activations. 100 zero crossing detections per second at 50 Hz.

You could use, in addition to, for example, INT0, the analog comparator or T0, which also overlaps INT0.

As mentioned above, the pins I am already using leave out INT0 and T0.

 

At some point in this development I thought about implementing the zero crossing detection with ADC3 samples, that would save me a couple of components in the circuit, but I discarded it because I did not get reliable results with the code.

 

I'll wait to see some guide to achieve this only with the timer, without the ISRs, if it really makes sense, as you put in doubt ...

Thank you!

 

Last Edited: Thu. Nov 9, 2017 - 12:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Kartman,

The only advantage I can see is that you might eliminate the isr latency when activating the port pin.

This is exactly what I'm looking for! among other advantages ...

 

Thanks! 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi El tangas! thanks for your answer!

 

Yes, that's what I was trying to do after reading the following on page 86 of the 08/2013 ATtiny25/45/85 datasheet:

COM1x1=1 / COM1x0=1


OC1x Set on compare match. Cleared when TCNT1= $00.

OC1x not connected.

My problem is that I don't know how. I'm very confused with the TCCR1 options I need for this (and overall datasheet redaction within Timer1 section).
Do I need to set CTC1=0, PWM1A=1, COM1A1=1 and COM1A0=1? In such case, what OCR register should I use for the compare match? Is it OCR1A? or OCR1C like in CTC mode?

Then, the pin change interrupt only needs to start the timer, while the overflow stops it (via prescaler).

My application needs to control at what point (timer tic count) after the pin change interrupt starts the timer, the triac is triggered, do I get this with the overflow?

 

Thank you!

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why not just always set PB1=0 in the PCINT0 interrupt ?
(You will get a variable pulse-width though).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Use OCR1A for the width.
The downsides of PWM mode are ;
- unwanted full-cycle turn-on when the mains-frequency decreases below the PWM frequency. (because COM1A will still be high after the zero-crossing).
- the minimum voltage output from the Triac will be higher (because the maximum OCR1A value is reduced)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi mikech thanks,

 

I've already tried that approach. The good thing is that it saves you the overflow ISR to turn PB1 off, bad thing is that, in practice, when PCINT ISR is executed, the real AC mains sine wave is positioned slightly after the zero point (at least with my HW), so It's too late to try to turn the triac off.

 

The actual effect is that the triac sometimes goes off, sometimes not.

So the intended power control gets trashed ...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'll try, tomorrow I'll tell you how it goes ...

Thanks!

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually your ISRs are very simple and could be written in assembly, this would save a lot of ISR overhead.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
Actually your ISRs are very simple and could be written in assembly, this would save a lot of ISR overhead.

Don't get me started on that again.  I can dig up many references to past battles, and/or you cn use this one as an example.

 

Make sure to identify this "overhead" as being related to the language chosen versus the toolchain (and its application) chosen.

 

 

casanovg wrote:
The good thing is that it saves you the overflow ISR to turn PB1 off, bad thing is that, in practice, when PCINT ISR is executed, the real AC mains sine wave is positioned slightly after the zero point (at least with my HW), so It's too late to try to turn the triac off.

I'm not versed in this mains/triac manipulation.  It would seem strange to me that the "holdoff" would be a full half cycle or more.  Guess I need to read up on typical firing sequences.  I guess a slow main clock and high prescaler would give enough reach, even with only 8-bit timers available.

 

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Don't get me started on that again. I can dig up many references to past battles, and/or you cn use this one as an example.

 

Ok. I didn't knew there were "wars" because of such a thing. So here is what I see:

 

// Timer1 Compare Match ISR (Triac switch on after counter compare match *fire delay*)
ISR(TIMER1_COMPA_vect) {
	PWR_CTRL_PORT |= (1 << PWR_CTRL_PIN);		/* Turn Triac On */
	TCNT1 = (255 - PULSELENGTH);			/* Set Timer1 counter for overflow interrupt */
}

 

compiles to (gcc):

00000078 <__vector_3>:

// Timer1 Compare Match ISR (Triac switch on after counter compare match *fire delay*)
ISR(TIMER1_COMPA_vect) {
  78:	1f 92       	push	r1
  7a:	0f 92       	push	r0
  7c:	0f b6       	in	r0, 0x3f	; 63
  7e:	0f 92       	push	r0
  80:	11 24       	eor	r1, r1
  82:	8f 93       	push	r24
	PWR_CTRL_PORT |= (1 << PWR_CTRL_PIN);		/* Turn Triac On */
  84:	c1 9a       	sbi	0x18, 1	; 24
	TCNT1 = (255 - PULSELENGTH);			/* Set Timer1 counter for overflow interrupt */
  86:	8c ef       	ldi	r24, 0xFC	; 252
  88:	8f bd       	out	0x2f, r24	; 47
}
  8a:	8f 91       	pop	r24
  8c:	0f 90       	pop	r0
  8e:	0f be       	out	0x3f, r0	; 63
  90:	0f 90       	pop	r0
  92:	1f 90       	pop	r1
  94:	18 95       	reti

Only r24 needs to be saved, the flags are not changed, r0 and r1 are not used, so why are they saved and restored? That's some 8 cycles overhead on entry, somewhat less on exit.

Last Edited: Thu. Nov 9, 2017 - 03:19 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
compiles to (gcc):

That is one of the points, choice of toolchain, that we may get to later.

El Tangas wrote:
Only r24 needs to be saved, the flags are not changed, r0 and r1 are not used, so why are they saved and restored? That's some 8 cycles overhead on entry, somewhat less on exit.

Because >>you<< chose to use that toolchain, and apply it in a particular manner.

 

I'll leave to the GCC gurus to address how to improve your shown code generation.  ...

OK, after some minor computer maintenance...

                 ;// Timer1 Compare Match ISR (Triac switch on after counter compare match *fire delay*)
                 ;interrupt [TIM1_COMPA] void timer1_compa_isr(void)
                 ;0000 0034 {
                 _timer1_compa_isr:
                 ;.FSTART _timer1_compa_isr
000042 93ea      	ST   -Y,R30
                 ;0000 0035     PWR_CTRL_PORT |= (1 << PWR_CTRL_PIN);        /* Turn Triac On */
000043 9ac1      	SBI  0x18,1
                 ;0000 0036     TCNT1 = (255 - PULSELENGTH);            /* Set Timer1 counter for overflow interrupt */
000044 efec      	LDI  R30,LOW(252)
000045 bdef      	OUT  0x2F,R30
                 ;0000 0037 }
000046 91e9      	LD   R30,Y+
000047 9518      	RETI
                 ;.FEND

Now, one can adjust one's tools.  In this case, if indeed critical, there can be a dedicated SREG register; there can be dedicated register variables for ISR use.

 

HOWEVER -- The "overhead" is like a microsecond?  Cut it to zero, and it won't really affect OP's situation.   [What problem are we solving again?  The overlap?  Is that at very low offset values?  need to see timing diagrams for the problem situation ans otherwise the times are slow...]

 

 

 

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Thu. Nov 9, 2017 - 04:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Who knows? Maybe the OP is actually using CodeVision, the toolchain was not mentioned.

 

Myself, I'm indeed very interested to know how to reduce the overhead in gcc.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:
Myself, I'm indeed very interested to know how to reduce the overhead in gcc.

Simply by waiting some time. ;-)

Georg-Johann has made changes in avr-gcc regarding the ISR overhead, but they haven't arrived in the released version yet.

 

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Theusch, about

I'm not versed in this mains/triac manipulation.  It would seem strange to me that the "holdoff" would be a full half cycle or more.

The problem with mikech's suggestion in post #8, is that setting PB1 = 0 within the PCINT ISR, produces an output like that of the medium to the right. As you can see, it is too late for the triac to turn off, since the sine wave is no longer on zero.

 

The code in the OP (the one I would like to improve) produces a pulse on PB1 nevertheless, as shown in the lower right, which allows a non-compromised triac switch off.

 

 

Last Edited: Thu. Nov 9, 2017 - 05:38 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@OP:

 

So have you tried PWM?

 

That is, this mode:

 

CTC1 = 0 (keep counting on match with OCR1A)

PWM1A = 1 (reset on match with OCR1C and enable PWM mode interpretation of COM1Ax)

COM1A1 = 1, COM1A0 =1 (output goes high on match with OCR1A, low on match with OCR1C)

 

Then just put the desired timings in OCR1A and OCR1C. If I understood the datasheet correctly, this should work, but I can't test.

 

sternst wrote:

El Tangas wrote:

Myself, I'm indeed very interested to know how to reduce the overhead in gcc.

 

Simply by waiting some time. ;-)

Georg-Johann has made changes in avr-gcc regarding the ISR overhead, but they haven't arrived in the released version yet.

 

 

You're right, in fact I just dl an avr-gcc 8.0 (experimental) build from https://osdn.net/projects/sfnet_... and it generates the optimized codelaugh

 

00000074 <__vector_3>:
  74:	8f 93       	push	r24
  76:	c1 9a       	sbi	0x18, 1	; 24
  78:	8c ef       	ldi	r24, 0xFC	; 252
  7a:	8f bd       	out	0x2f, r24	; 47
  7c:	8f 91       	pop	r24
  7e:	18 95       	reti

 

Last Edited: Thu. Nov 9, 2017 - 05:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

 

mikech wrote:
Use OCR1A for the width.
 

El Tangas wrote:
@OP:   So have you tried PWM?

CTC1 = 0 (keep counting on match with OCR1A) PWM1A = 1 (reset on match with OCR1C and enable PWM mode interpretation of COM1Ax) COM1A1 = 1, COM1A0 =1 (output goes high on match with OCR1A, low on match with OCR1C)

 

Well, yes and no, unfortunately, I made a bad movement and blew up a rectifier on the ZCD. So I had to test the PWM approach by simulating the ZCD pulse with the watchdog. Apparently, from what I see in the oscilloscope, it would be working, it produces a pulse on PB1. However, I have to wait until the night to repair the ZCD and test the whole thing properly. That is why I haven't posted the results before ...

 

Nonetheless, the outcome is seemingly very encouraging! The improvements with this approach, so far, are:

 

1. I no longer handle PB1 in code within the ISRs, it's handled by Timer1's OC1A (Yeah!), so these lines can be eliminated:
PWR_CTRL_PORT | = (1 << PWR_CTRL_PIN);
PWR_CTRL_PORT & = ~ (1 << PWR_CTRL_PIN);

 

2. I also eliminated the Timer1 Overflow ISR (with its associated latency), since Timer1 sets PB1 to low when overflow occurs. No more ISR(TIMER1_OVF_vect) { ... }

 

3. These changes produce a .hex file 94 bytes smaller, around 10% less to get the same operation!

 

However, I have kept the Timer1 compare match ISR in the tests I did so far. This is to force the timer overflow by using "TCNT1 = (255 - PULSELENGTH);", so preserving the pulse behavior on PB1.

 

I am very interested in this that you mention:

(output goes high on match with OCR1A, low on match with OCR1C)

Because if it's really like that, I could control the triac trigger pulse delay with OCR1A and the duration of the pulse with OCR1C, also eliminating the compare match ISR.

 

If everything goes well, I'll publish the final C code tonight ...

 

Last Edited: Thu. Nov 9, 2017 - 07:05 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I expected the start-of-a-cycle edge-detection to occur slightly after the start of a cycle, but I did not expect the end-of-a-cycle edge-detection to occur *after* the end of the cycle.
Perhaps the total time between edge-detection *and* triac firing is excessive. (how are you triggering the triac from the micro ?)
I am curious top see the actual ZCD circuit.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, you're right, in the drawing I posted, there's a mix between what the ZCD actually does and the pin change ISR outcome, that actively skips the end-of-a-cycle edge-detection with an "IF":

ISR(PCINT0_vect) {
	if(ZCD_INP & (1 << ZCD_PIN)) {		/* If it was triggered by a rising edge, run ISR code */
	}
}

Perhaps the total time between edge-detection *and* triac firing is excessive. (how are you triggering the triac from the micro ?)

The time between edge-detection and triac triggering is controlled (delayed on purpose) by OCR1C (OCR1A in PWM) to control power on the AC load.

I'm triggering the triac with a MOC3052 opto.

mikech wrote:
I am curious top see the actual ZCD circuit.

The circuit that I am using is cheap and quite rudimentary:

 

 

Last Edited: Fri. Nov 10, 2017 - 07:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My quick LTSPICE simulation of your input ;

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi mikech,

 

Thanks for the simulation! Now I remember better (I did this part very early in the project), the peaks are aligned with the zeros of the AC sine wave thanks to the "NPNness" of the opto, right? So, the Tiny85 efective "zero" detection occurs at some point of the ZCD signal rising edge.

 

This is what brought me some complications at the beginning since the point of detection was not completely stable. Maybe with a little more work to better adjust the ZCD electronics this can be solved and the pin change ISR can be used to turn the triac control pin off (3rd graphic).

 

Anyway, I insist, IMO it is better to use a short pulse (the shortest supported by the triac) for the triggering (4th graphic). Why risk that the triac does not turn off on time by playing at the limit?

 

Attachment(s): 

Last Edited: Fri. Nov 10, 2017 - 02:34 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi folks,

 

Finally, I was able to repair the ZCD and test everything: The PWM approach worked perfectly! I can confirm that a match of TCNT1 with OCR1A puts PB1 on high, and a match with OCR1C puts it on low.

 

The aim of this post have been fully fulfilled for me, thank you very much to all for helping me to understand better how to configure the timer in PWM. I publish the latest version of the code below, in case it is useful for someone.

 

My summary is:

  • With some adjustment, it is also possible to use PWM for power control applications in low frequency (50 - 60 Hz), I thought differently at the beginning of the project.
  • To the advantages of the PWM vs CTC approach listed in post # 20, with this latest version its possible to add:
    • The comparison match ISR was also eliminated so that no ISR associated with the timer is longer used (less latency). Only the pin change ISR is used for the zero crossing detection (maybe in the future I'll work again on the possibility of doing this with samples of the ADC, since it would have the extra advantage of being able to "measure" the AC line voltage).
    • avr-size shows 290 bytes with this code, vs 344 bytes of the OP CTC version, this is around 15% less flash memory for the same function.

 

Next step for me is to get the smallest possible piece of code for line frequency autodetection (the gadget should be aware of whether is plugged into a 50 or 60 Hz mains).

 

// Includes ...

// Defines
#define PWR_CTRL_PIN 	PB1
#define PWR_CTRL_DDR	DDRB
#define PWR_CTRL_PORT	PORTB
#define ZCD_PIN		PB3
#define ZCD_DDR		DDRB
#define ZCD_PORT	PORTB
#define ZCD_INP		PINB
#define CHANGETIME	0xFFFF
#define MINGATEDELAY 	7		/* OK @50Hz 7 tics after ZCD */
#define MAXGATEDELAY 	150		/* OK @50Hz 150 tics are the maximum safe triac switching *FURTHER TESTING NEEDED* */
#define PULSELENGTH 	3		/* OK @50Hz 3 tics are enough to fire the triac */

// Global variables
volatile uint16_t ocrChangeDelay = 0;
volatile uint8_t newValueFromI2C = UsiTwiReceiveByte();

// Function prototypes ...

// Function Main
int main(void) {
	// Setup
	Setup();
	// Loop
	Loop();
	return 0;
}

// Pin Change ISR (Zero Cross Detection)
ISR(PCINT0_vect) {
	if(ZCD_INP & (1 << ZCD_PIN)) {			/* If it was triggered by a rising edge, run ISR code */
		TCNT1 = 0;				/* Reset Timer1 counter *MAYBE THIS IS NOT NEEDED* */
		TCCR1 |= ((1 << CS13) | (1 << CS11));	/* Start Timer1 with prescaler 512 (I/O clock / 512) */
	}
}

// Function Setup
void Setup(void) {
	clock_prescale_set(8);				/* Set CPU @1 MHz (System clock / 8) */
	PWR_CTRL_DDR |= (1 << PWR_CTRL_PIN);      	/* Set led pin Data Direction Register for output */
	PWR_CTRL_PORT &= ~(1 << PWR_CTRL_PIN);		/* Force triac off, just in case ... */
	_delay_ms(500);					/* Delay to allow ISP programming at 1 MHz after power on */
	clock_prescale_set(1);				/* Set CPU @8 MHz (System clock / 1) for safe I2C slave operation */
	cli();						/* Disable interrupts */
	SetPinChangeInterrupt();			/* Enable pin change interrupt */
	SetTimer1PWM();					/* Enable Timer1 PWM */
	sei();						/* Enable interrupts */
}

// Function Loop
void Loop (void) {
	for(;;) {
		OCR1A = newValueFromI2C;               /* Increase the OCR1A compare register value to reduce AC power */
		OCR1C = OCR1A + PULSELENGTH;           /* OCR1C register value handles the triac trigger pulse length */
	}
}

// Function SetPinChangeInterrupt sets PCINT3 interrupt for zero cross detection
void SetPinChangeInterrupt(void) {
	ZCD_DDR &= ~(1 << ZCD_PIN);			/* Set PB3 for input to detect the Zero Cross pulse */
	ZCD_PORT &= ~(1 << ZCD_PIN);			/* Disable PB3 pull-up resistor (high-impedance state) */
	GIMSK |= (1 << PCIE);				/* Enable Pin Change Interrupt */
	PCMSK |= (1 << PCINT3);				/* Set Pin Change Mask Register to allow ZCD on PB3 */
}

// Function SetTimer1PWM sets Timer1 PWM for modulating phase angle
void SetTimer1PWM(void) {
	TCCR1 |= (1 << PWM1A);				/* Set Timer1 PWM mode */
        TCCR1 |= ((1 << COM1A1) | (1 << COM1A0));       /* Set Timer1 PWM OC1A output pin control *PB1 pin to control the triac gate* */
	OCR1A = MINGATEDELAY;				/* Set Timer1 OCR1A register to control triac trigger delay after ZCD */
	OCR1C = OCR1A + PULSELENGTH;			/* Set Timer1 OCR1C register to control triac trigger pulse length */
}   

 

Last Edited: Fri. Nov 10, 2017 - 06:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The circuit that I am using is cheap and quite rudimentary:

A better one:

http://www.dextrel.net/diyzerocrosser.htm

 

Centres the pulse around actual ZC, making it easy to predict the actual ZC by remembering the timing of (one or more) previous pulse(s).

 

Claimed features:

  • highly accurate mains zero crossing detection
  • fully isolated and low voltage safe output
  • ultra-low power consumption; worst case power dissipation < 120mW
  • produces symmetrical pulses around zero crossings
  • output pulse stays constant, independent of the mains voltage
  • very low parts count, no precision components required
  • all components can be low voltage SMD
  • works over all voltage ranges (100VAC/240VAC), without modification
  • 50Hz/60Hz compatible
  • 50Hz produces a 1ms ZC pulse, 60Hz produces 0.83ms ZC pulse
  • highly stable with varying temperature and aging

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not bad at all, thank you!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I just pick off the ac from the secondary of the transformer, a little filtering then into the input capture. My code uses a phase lock loop technique to recover the zero cross and provide phase correction. It probably wouldn’t work too well using an 8 bit timer.

Also, with the zx link, it is not a good idea to use 1/8w resistors on 240vac - the voltage rating is not adequate.

Last Edited: Sat. Nov 11, 2017 - 02:28 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Also, with the zx link, it is not a good idea to use 1/8w resistors on 240vac - the voltage rating is not adequate.

All components are low cost and low voltage types (<10V), with the exception that R1 and R2 must sustain each half of the maximum peak mains voltage (200 volts). R2 can be omitted if R1 alone can sustain the maximum peak input voltage. In this case use a 390kohm standard resistor. Splitting the input resistor into two (halving the voltage handling requirement) makes it possible to use only SMD-components. 

 

 

My code uses a phase lock loop technique to recover the zero cross and provide phase correction.

I do something similar.

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
I just pick off the ac from the secondary of the transformer, a little filtering then into the input capture. My code uses a phase lock loop technique to recover the zero cross and provide phase correction

That's roughly my next step, but I don't have a transformer working at line frequency in my design. So I'll have to pick the AC amplitude and frequency out of an optotransistor. The circuit works at 3v3, so the sine wave would have that Vpp amplitude at the most. Not sure if it's going to be reliable for "measuring" voltage, but should be enough to infer the zero cross, what do you think?

And yes, only 8-bit timers available on the Tiny85.

Last Edited: Sat. Nov 11, 2017 - 12:17 PM