AC Dimmer

Post here first, or if you can't find a relevant section!
pokemon99
Posts: 50
Joined: Wed Jun 07, 2017 9:09 am

Re: AC Dimmer

Post by pokemon99 » Thu Dec 14, 2017 10:24 am

Ok, when I make a new one, I'll upload the photo.

User avatar
RogerClark
Posts: 7681
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: AC Dimmer

Post by RogerClark » Thu Feb 08, 2018 12:51 am

I've done some more testing with my mains dimmer control, and using exactly the same circuit as http://dextrel.net/diyzerocrosser.htm

My pulse widths are 1.25mS rather than 1mS (500us before and after the crossing)
zero_crossing.png
zero_crossing.png (6.28 KiB) Viewed 146 times
I've modeled the circuit in LTSpice and it looks like the pulses are fairly symmetrical around the actual zero crossing point

And I can reduce the pulse width by increasing capacitor from 10uF to 22uF and reducing R4 from 1k to 220 ohms
(I've checked the datasheet on the 4N35 and its max current on the LED is 50mA, and this circuit delivers a pulse of around 4mA (either in its original form or my modified form)

Actually the higher value of R4 seems to make the pulse squarer but a bit wider

So I'm not sure of the optimum value

The main thing, is this pulse needs to be as short as possible, yet be clean.

Because the triac can't be triggered to turn on until the falling edge after the zero crossing, so it limits the maximum power and also the minimum power, if the rising edge is used to turn the triac off.

I notice in a lot of dimmer control software, that they have limits to the minimum and maximum power settings for which they actually use a delayed start

e.g. if > 95% is selected a lot of code just turns the triac on all the time.
Also, below 5%, they just don't turn the triac on at all.

I've modeled the power percentage (using a spreadsheet) vs the width of the unusable window, and its approximately the lower and upper 0.25%

Which is 5W for a 2kW load and 10W for a 4kW load.

So even with the existing 1.25mS wide pulse its totally usable, to control to a resolution of 10W on a domestic water heater
Attachments
zero_crossing_detector.zip
(646 Bytes) Downloaded 3 times

User avatar
RogerClark
Posts: 7681
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: AC Dimmer

Post by RogerClark » Thu Feb 08, 2018 9:53 am

My test code

Code: Select all

const int  ZERO_CROSSING_PIN =14;
const int  TRIC_TRIGGER_PIN =13;
           
volatile boolean zeroCrossingState = 0;
uint16_t dimming = 500; 
char buf[16];
char *bPos;

void zero_cross_detect() 
{
  zeroCrossingState = digitalRead(ZERO_CROSSING_PIN);
  if (zeroCrossingState==0)
  {
    // end of half cycle
    Timer2.pause();// cant trigger the tric now, its too late !
    Timer2.setCount(0);// get ready to start again
    Timer2.setOverflow(dimming);
    Timer2.refresh();
    digitalWrite(TRIC_TRIGGER_PIN,LOW);
  }
  else
  {
   // beginning of half cycle 
      Timer2.resume();     
  }
}
void triggerDelayHandler()
{
  // Can't turn the triac while the zero crossing input is low
  Timer2.pause();
  if (zeroCrossingState)
  {
      digitalWrite(TRIC_TRIGGER_PIN,HIGH);// Turn on the triac
  }
}
void setup()
{
  dimming = 750;// global volatile

  pinMode(TRIC_TRIGGER_PIN, OUTPUT);// Set AC Load pin as output
  pinMode(ZERO_CROSSING_PIN,INPUT);
  digitalWrite(TRIC_TRIGGER_PIN,LOW);// Turn on the triac
  attachInterrupt(ZERO_CROSSING_PIN, zero_cross_detect, CHANGE);

  Timer2.pause();
  Timer2.setChannel1Mode(TIMER_OUTPUTCOMPARE);
  Timer2.setPrescaleFactor(720);
  Timer2.setOverflow(dimming);
  Timer3.setCount(0);
  Timer2.attachCompare1Interrupt(triggerDelayHandler);
}


void loop()
{
  if (Serial.available())
  {
    bPos=buf;
    while(Serial.available())
    {
      *bPos++ = Serial.read();
    }
    *bPos++='\0';
    dimming=atoi(buf);
  }
}
Accepts values from serial terminal for delay

1000 = 10 ms

Currently there is a bug,

If I set delay of 1 (or possibly other low numbers) the code seems to hang

Not sure why

I still need to investigate that problem

User avatar
RogerClark
Posts: 7681
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: AC Dimmer

Post by RogerClark » Thu Feb 08, 2018 9:01 pm

Thinking about the problem where the code hangs if I set a low value e.g. 1 for Overflow.

I susoect what may be happening, is that the Timer is not being paused in the ISR, before the next interrupt arrives.

I know there is higher latency on some interrupts than others as they have shared vectors, but I will need to check if Timer2 shares its vector.

The prescaler is set to 720, so that each count is 100,000 clock cycles, so I am surprised that setting an overflow of 1 , means that the ISR does not get serviced in time to pause the Timer

Perhaps a value of 1 causes an immediately trigger of ISR?

User avatar
RogerClark
Posts: 7681
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: AC Dimmer

Post by RogerClark » Thu Feb 08, 2018 9:35 pm

Reading the docs for the F103, it has a one pulse mode
14.3.15 One-pulse mode
Starting the counter can be controlled through the slave mode controller. Generating thewaveform can be done in output compare mode or PWM mode. Select One-pulse mode by
setting the OPM bit in the TIMx_CR1 register. This makes the counter stop automatically at
the next update event UEV.
A pulse can be correctly generated only if the compare value is different from the counter
initial value. Before starting (when the timer is waiting for the trigger), the configuration must
be:
• In upcounting: CNT < CCRx ≤ ARR (in particular, 0 < CCRx)
• In downcounting: CNT > CCRx
stm32_one_pulse_mode.png
stm32_one_pulse_mode.png (16.57 KiB) Viewed 115 times

For example the user may want to generate a positive pulse on OC1 with a length of tPULSE
and after a delay of tDELAY as soon as a positive edge is detected on the TI2 input pin.
Let’s use TI2FP2 as trigger 1:
• Map TI2FP2 to TI2 by writing CC2S=’01’ in the TIMx_CCMR1 register.
• TI2FP2 must detect a rising edge, write CC2P=’0’ in the TIMx_CCER register.
• Configure TI2FP2 as trigger for the slave mode controller (TRGI) by writing TS=’110’ in
the TIMx_SMCR register.
• TI2FP2 is used to start the counter by writing SMS to ‘110’ in the TIMx_SMCR register
(trigger mode).
The OPM waveform is defined by writing the compare registers (taking into account the
clock frequency and the counter prescaler).
• The tDELAY is defined by the value written in the TIMx_CCR1 register.
• The tPULSE is defined by the difference between the auto-reload value and the compare
value (TIMx_ARR - TIMx_CCR1).
• Let us say the user wants to build a waveform with a transition from ‘0’ to ‘1’ when a compare match occurs and a transition from ‘1’ to ‘0’ when the counter reaches the auto-reload value. To do this, enable PWM mode 2 by writing OC1M=111 in the TIMx_CCMR1 register. The user can optionally enable the preload registers by writing OC1PE=’1’ in the TIMx_CCMR1 register and ARPE in the TIMx_CR1 register. In this case the compare value must be written in the TIMx_CCR1 register, the auto-reload value in the TIMx_ARR register, generate an update by setting the UG bit and wait for external trigger event on TI2. CC1P is written to ‘0’ in this example.

In our example, the DIR and CMS bits in the TIMx_CR1 register should be low.
The user only wants one pulse (Single mode), so '1’ must be written in the OPM bit in the
TIMx_CR1 register to stop the counter at the next update event (when the counter rolls over
from the auto-reload value back to 0). When OPM bit in the TIMx_CR1 register is set to '0',
so the Repetitive Mode is selected.
Particular case: OCx fast enable:
In One-pulse mode, the edge detection on TIx input set the CEN bit which enables the
counter. Then the comparison between the counter and the compare value makes the
output toggle. But several clock cycles are needed for these operations and it limits the
minimum delay tDELAY min we can get.
If the user wants to output a waveform with the minimum delay, the OCxFE bit in the
TIMx_CCMRx register must be set. Then OCxRef (and OCx) are forced in response to the
stimulus, without taking in account the comparison. Its new level is the same as if a compare
match had occurred. OCxFE acts only if the channel is configured in PWM1 or PWM2
mode.
This mode sounds ideal for a dimmer application.

However I think that setting up the timer correctly to be triggered by the correct pin and output correctly may take quite a long time to figure out

dannyf
Posts: 228
Joined: Wed May 11, 2016 4:29 pm

Re: AC Dimmer

Post by dannyf » Fri Feb 09, 2018 6:55 pm

an interesting discussion. my 2 cents:

1) I think a 32-bit chip for this is an overkill. an 8-bit chip can do it without breaking a sweat.

2) if you are looking for a speedy execution, I would suggest that your initialize the peripherals outside of the isr. this generally can be done with two isrs (a exti + timer), but sometimes one isr (timer) will work as well - if you load up the timer isr with an -1 offset, it becomes an exti isr.

3) you can greatly simplify the zero cross circuitry, especially if you assume the positive half and the negative half are symmetrical.

4) ST has an appnote for this utilizing STM8. using the input thresholds of a digital pin to detect zero crossing. I can provide a link if you are interested.

6) for those extremely adventurous, you can switch an AC load with a bridge + mosfet. the lowest cost solution.

User avatar
RogerClark
Posts: 7681
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: AC Dimmer

Post by RogerClark » Fri Feb 09, 2018 8:19 pm

@dannyf

I agree that a 32 bit MCU is overkill for just operating the dimmer.

However, I didn’t outline the whole system. It’s part of a control system, for electrical loads, ( mainly water heating, but also oil filled radiators, and other electrical heaters where the deman can be modulated)

The purpose of the system is to use spare solar electrical generation capacity , rather than the power being sent to the grid; as the Feed In Tariff for solar is either zero ( in this particular case) or a small percentage of the buy back price ( in most cases)

Hence it pays to find some way to use the solar electricity yourself.
Heating water is generally more cost effective than battery storage of the power, as a lot of people already have electric water heaters.

The same STM32 that I intend to control this, is already running a disolay unit, which receives data via a 433 MHz link, ( nRF905 ) And turns the load on and off using a relay.


In the enhanced system the STM32 still has to receive the data every 500 mS and update the disolay, also I intend to use a Hall effect current sensor ACS712 to monitor the actual power taken by the load and run a control system using PID.
( I am going to use the Hal sensor, as updates every 500 mS from the master power monitor system make the PID system rather slow at controlling the dimming to achieve the optimum demand to consume all available free power as soon as possible )

Hence offloading the pulse delay and pulse width, to the Timer hardware makes sense as it will not get affected by the other things the STM32 is doing.

dannyf
Posts: 228
Joined: Wed May 11, 2016 4:29 pm

Re: AC Dimmer

Post by dannyf » Sun Feb 11, 2018 12:39 am

With the water heaters time constant be so large, you may not find much benefits in going PID/pwm/Pam.

I did PID + pwm on a solder iron build and minimum benefits between that and the simple on/off control.

But if you just want to play with it, that's another story.

User avatar
RogerClark
Posts: 7681
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: AC Dimmer

Post by RogerClark » Sun Feb 11, 2018 3:00 am

@dannyf

Re: On / Off or PWM vs dimming

I think it probably depends on how the electricity company measures the power you are consuming or exporting.

I'm actually building this for a fairly small scale water heater (1kW or 2kW), though it may get used for larger things

The existing system uses a relay, but because its for use in the UK where they often get a lot of cloud cover and low angle of the sun, so solar production on a 4kW array may only be 500W. Then simply tuning on the heater when there is 1kW or 2kW available, means that the system doesnt turn on much in the winter if the sun is not that high in the sky etc etc

Conventional PWM won't work, because the cheaply available power controllers use a triac rather than power FET.

And by the nature of sine waves, most of the power is in the middle of each half wave.

I guess using a FET with PWM much faster than mains frequency would work.

However for some reason FETs used in all the inverter I've played with , seem to have a nasty habit of being destroyed by spikes on the mains

And I've learn t by experience that the $5 600V 30A FETs on eBay are junk.



Anyway. I've actually got the system working now ;-)

User avatar
RogerClark
Posts: 7681
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: AC Dimmer

Post by RogerClark » Sun Feb 11, 2018 5:58 am

I know this a bit of a cross posting....

But, here is the code I'm using to test ramping the dimmer up and down

Code: Select all

#include <Streaming.h>
uint16_t pulseDelay = 8500;
uint16_t pulseWidth = 100;
void setup()
{
  pinMode(PA1, PWM);  // setup PA1 (Timer2 channel 2) to PWM (one pulse mode)
  pinMode(PA0, INPUT);  // setup PA0 (Timer 2 channel 1) as input (capture input mode)

  Timer2.pause();  // stop the timers before configuring them

  timer_oc_set_mode(TIMER2, 2, TIMER_OC_MODE_PWM_2, TIMER_OC_PE);

  Timer2.setPrescaleFactor(72); // 1 microsecond resolution
  Timer2.setOverflow(pulseWidth + pulseDelay-1);
  Timer2.setCompare(TIMER_CH2, pulseDelay);


  // counter setup in one pulse mode, as slave triggered by External input for Timer 2
  TIMER2_BASE->CR1  = ( TIMER_CR1_OPM ); // one pulse mode
  TIMER2_BASE->SMCR = ( TIMER_SMCR_TS_ETRF | TIMER_SMCR_SMS_TRIGGER );

  TIMER2_BASE->CCER  = ( TIMER_CCER_CC1E | TIMER_CCER_CC2E );  // enable channels 1 and 2

  Timer2.refresh();  // start timer 2
  Timer2.resume(); // let timer 2 run

}

void updateDelay(uint16_t dly)
{
   // Timer2.pause();
    Timer2.setOverflow(pulseWidth + dly-1);    
    Timer2.setCompare(TIMER_CH2, dly);
   // Timer2.refresh();  // start timer 2
}

char buf[16];
char *bPos;
int direction=1;
int increment=20;

const int minDelay=0;
const int maxDelay=9000;

uint16_t oldPulseDelay;
void loop3()
{
  Serial.println(Timer2.getCount());
}
void loop()
{

  if (direction==1)
  {
    if (pulseDelay<maxDelay)
    {
      pulseDelay+=increment;
    }
    else
    {
      direction=0;
      pulseDelay-=increment;
    }
  }
  else
  {
    if (pulseDelay>minDelay)
    {
      pulseDelay-=increment;
    }
    else
    {
      direction=1;
      pulseDelay+=increment;
    }

  }
  while(Timer2.getCount() !=0);
  updateDelay(pulseDelay);
  delay(50);


  if (Serial.available())
  {
    bPos=buf;
    while(Serial.available())
    {
      *bPos++ = Serial.read();
    }
    *bPos++='\0';
    updateDelay(atoi(buf));
  }
}

uint32_t t;
void loop2()
{
  if ( (millis()-t)>1000 )
  {
    t = millis();
    Serial << millis()
      << ", TIM2->CCMR1: " << _HEX(TIMER2_BASE->CCMR1)
      << ", TIM2->CCER: " << _HEX(TIMER2_BASE->CCER)
      << ", TIM2->SMCR: " << _HEX(TIMER2_BASE->SMCR)
      << ", TIM3->CCMR1: " << _HEX(TIMER3_BASE->CCMR1)
      << ", TIM3->CCER: " << _HEX(TIMER3_BASE->CCER)
      << ", TIM3->SMCR: " << _HEX(TIMER3_BASE->SMCR) << endl;
  }
}
One key thing to note, is the counter registers should not be updated while the counter is counting, as this causes all sorts of strange results.

So I'm synchronising the change in the dead period between the end of one pulse and the beginning of the next.

In my case I have loads of time for this, as the pulse is 1200uS wide.

If the dead time was significantly shorter this code would need to be optimisied by directly reading the counter and directly setting the 2 registers in question

Post Reply