AC Mains Cycle Phase Control

Status
Not open for further replies.

gfvalvo

Well-known member
Hi. I’m wondering if it’s possible to do 60 Hz AC mains phase control (i.e. incandescent light dimming) using the T3.x FlexTimer Module (FTM) without direct software intervention at the per-cycle time scale.

I’ve scanned the FTM section in the datasheet and see that it has lots of different modes that I’ll go back and study in detail. But, if someone can say for certain that it CAN’T do what I’m proposing, then I’ll save the effort for now.

My basic idea is to start the FTM counting (from 0) when the AC cycle zero crossing is detected. The match value (CnV) and terminal count (MOD) will have already been set up by software based on the amount of dimming desired. When the FTM counter reaches CnV, an output pulse will be begun to trigger the triac’s gate at the desired phase of the AC cycle. Shortly thereafter, MOD count will be reached, and the timer will overflow. This will terminate the output pulse so that the triac’s gate is not asserted for when the current half cycle ends.

Key to this idea is being able to sync the start of FTM counting to the AC cycle zero crossing and to ensure that counting does not start again after overflow until the next crossing occurs.

As an alternative, I’m wonder if I could sync one of the PWM modes to the AC cycle zero crossing and adjust the PWM output characteristics to get the same behavior.

So, does anyone know for sure that this is not possible?

Thanks.
 
Hi Paul, thanks for the reply. Yes, I’m sure T3.x is more than capable of doing this in software. I’m pursuing the hardware technique as somewhat of a learning exercise.

Do you know if FTM can be operated in the mode I outlined? Mainly:

* Start counting when triggered by external pulse.

* Stop counting after overflow and don’t start again until the next trigger.
 
* Start counting when triggered by external pulse.

Maybe, but I have not ever tried this. I would use an interrupt and have the interrupt code configure the timer.

The FTM timers do have special fault inputs. I've never used them, so I really can't suggest anything more specific than reading the FTM chapter carefully.


* Stop counting after overflow and don’t start again until the next trigger.

Again, I've almost always used the FTM timers in free running mode, so really not sure about this.

Personally, I would not do things this way, starting and stopping the timer. I would configure it to count from 0 to 0xFFFF and roll over. Then I'd use 1 channel as input capture, to capture the free-running count at the zero cross detection. Then I'd use another channel as output compare, to generate the pulse at a desired time in the future... where the input capture interrupt programs the output compare channel. Then I'd have that channel interrupt, and in that ISR I'd reprogram it to again change the pin in the future, for the other edge of the pulse. This way does involve interrupts for every event, but the hardware does the actual work precisely aligned to the timer count and the interrupts clean up after each event and program what happen at the next event.
 
Thanks for the suggestion, Paul.

I do see at least one advantage to doing it your way - I could use the FTM counter value captured at every zero crossing to determine the half-cycle period of the AC line in units of counter ticks. That would make it easy to scale the desired brightness into counts for the output compare event. This way I would not have to determine a priori the number of counts that occur during a half cycle. It would also automatically account for drift and unit-to-unit variation of the T3.x’s clock rate.
 
Personally, I would not do things this way, starting and stopping the timer. I would configure it to count from 0 to 0xFFFF and roll over. Then I'd use 1 channel as input capture, to capture the free-running count at the zero cross detection. Then I'd use another channel as output compare, to generate the pulse at a desired time in the future... where the input capture interrupt programs the output compare channel. Then I'd have that channel interrupt, and in that ISR I'd reprogram it to again change the pin in the future, for the other edge of the pulse. This way does involve interrupts for every event, but the hardware does the actual work precisely aligned to the timer count and the interrupts clean up after each event and program what happen at the next event.

Sounds like the most reliable solution to me.
 
So, as a learning exercise, I decided to see if I could make it work using only the hardware without firmware involvement. Turns out, it’s possible. See code below. The firmware’s only jobs are initial set up and updating 2 registers when a phase change is required. Other than that, the hardware does all pulse generation on its own.

The setup runs two FTM channels in “Combine Mode” to generate the brief pulse that will trigger the triac. The FTM’s counter is reset on every zero crossing of the AC cycle (via hardware synchronization mode) to synch it up. The zero crossing detect signal is feed into the Analog Comparator which issues a hardware synch trigger on every rising edge. I needed to use the CMP because the other processor pins that are capable of generating an FTM hardware sync trigger are already used on the T3.2 board for the 16MHz xtal.

I don’t have the AC mains circuitry connected yet, I’m simulating the zero crossing detect with a pulse stream generated on another board. The pulses (yellow in the scope shots below) repeat at 120 Hz. I arbitrarily set their width to 500us. I’m sure that’s way too wide and I’ll need to check operation with much narrower pulses as I suspect the actual circuitry will produce.

The triac trigger signal (green in scope shots) is set to 10us, again an arbitrary number. The scope shots show 3 different positions of the trigger based on desired phase of the triac’s activation (low, medium, high brightness).

Since the counter should never overflow in normal operation, I plan to use the TOF interrupt to indicate loss of zero crossing detect pulses and shut down the triac until they return.

Code:
#include <Arduino.h>

void initCmp();
void initFtm();

volatile bool ftmOverflow = false;

#define FTM1_CH0_PIN 3
#define FTM1_CH1_PIN 4

void setup() {
	Serial.begin(115200);
	delay(2000);
	Serial.println("Starting");

	initCmp();
	initFtm();

}

void loop() {
	bool newSetting = false;
	static int16_t setting = 0;

	while (Serial.available()) {
		char c = Serial.read();
		switch (c) {
			case '+':
				newSetting = true;
				setting += 20;
				if (setting > 255) {
					setting = 255;
				}
				break;

			case '-':
				newSetting = true;
				setting -= 20;
				if (setting < 0) {
					setting = 0;
				}
				break;

			default:
				break;
		}
		if (newSetting) {
			Serial.print("Set Point = ");
			Serial.println(setting);
			uint32_t turnOnPoint = (255 - setting) * 192;
			FTM1_SYNCONF &= ~FTM_SYNCONF_HWWRBUF;
			FTM1_C0V = turnOnPoint;
			FTM1_C1V = turnOnPoint + 60;
			FTM1_SYNCONF |= FTM_SYNCONF_HWWRBUF;
		}

	}

	if (ftmOverflow) {
		ftmOverflow = false;
		Serial.println("TOF");
	}
}

void ftm1_isr() {
	if (FTM1_SC & FTM_SC_TOF) {
		ftmOverflow = true;
		FTM1_SC &= ~FTM_SC_TOF;
	}
}

void initCmp() {
	CORE_PIN11_CONFIG = PORT_PCR_MUX(0);			// Set Pin 11 for CMP0 IN0
	SIM_SCGC4 |= SIM_SCGC4_CMP;						// Enable clock to CMP
	CMP0_CR1 = 0;									// Reset control registers
	CMP0_CR0 = 0;
	CMP0_CR1 = CMP_CR1_PMODE | CMP_CR1_EN;			// Enable CMP, High Speed mode
	CMP0_FPR = 0;									// Filter off
	CMP0_DACCR = CMP_DACCR_DACEN | CMP_DACCR_VRSEL | CMP_DACCR_VOSEL(0x1F);		// Compare Pin 11 to DAC Output
	CMP0_MUXCR = CMP_MUXCR_PSEL(0) | CMP_MUXCR_MSEL(7);							// Enable and setup DAC
	CMP0_SCR = CMP_SCR_CFR | CMP_SCR_CFF;										// Clear status flags
}

void initFtm() {
	pinMode(FTM1_CH0_PIN, OUTPUT);
	digitalWrite(FTM1_CH0_PIN, LOW);

	FTM1_SC = FTM1_SC & 0;    // Reset counter control, Force read-modify-write to clear TOF flag
	FTM1_MODE = FTM_MODE_FTMEN; // Enhanced FTM Mode
	FTM1_OUTINIT = 0;     // All channel outputs initialized to low

	FTM1_CNT = 0000;      // Reset counter
	FTM1_MOD = 0xFFFF;      // Terminal count
	FTM1_CNTIN = 0;       // Counter reload value

	NVIC_SET_PRIORITY(IRQ_FTM1, 48);

	FTM1_C0SC = FTM1_C0SC & 0;                    // Force read-modify-write, clears CHF flag
	FTM1_C0SC = FTM_CSC_MSB | FTM_CSC_ELSB;      // Active high CH0 output pulses in combined mode
	//FTM1_C0V = 48960;                       // CH0 output compare value
	FTM1_C0V = 0;                       // CH0 output compare value

	FTM1_C1SC = FTM1_C1SC & 0;                    // Force read-modify-write, clears CHF flag
	FTM1_C1SC = FTM_CSC_MSB;             // Active high CH1 output pulses in combined mode
	//FTM1_C1V = 48960;                       // CH1 output compare value
	FTM1_C1V = 0;                       // CH1 output compare value

	FTM1_COMBINE = FTM_COMBINE_COMBINE0;              // Combine channels 0 and 1
	FTM1_COMBINE |= FTM_COMBINE_SYNCEN0;              // Enable sync CV0, CV1

	*portConfigRegister(FTM1_CH0_PIN) = PORT_PCR_MUX(3);      // CH0 pin

	FTM1_MODE |= FTM_MODE_INIT;                   // Initialize channel outputs

	FTM1_SYNCONF = FTM_SYNCONF_SYNCMODE;              // Enhanced sync mode

	FTM1_SYNCONF |= FTM_SYNCONF_HWRSTCNT;             // Select HW counter sync trigger
	FTM1_SYNCONF |= FTM_SYNCONF_HWWRBUF;              // HW sync of MOD, CNTIN, and CV registers
	FTM1_SYNCONF |= FTM_SYNCONF_HWTRIGMODE;
	FTM1_SYNC = FTM_SYNC_TRIG0;

	FTM1_SC = FTM_SC_TOIE;				// Enable overflow interrupt
	FTM1_SC |= FTM_SC_PS(3);			// prescaler = 8
	FTM1_SC |= FTM_SC_CLKS(1);			// run from sys clk

	NVIC_ENABLE_IRQ(IRQ_FTM1);
}
 

Attachments

  • w1.jpg
    w1.jpg
    112.8 KB · Views: 102
  • w2.jpg
    w2.jpg
    112.9 KB · Views: 93
  • w3.jpg
    w3.jpg
    112.6 KB · Views: 66
Status
Not open for further replies.
Back
Top