Arduino AC Power Shield!

betwys, I agree I use alot of SCR's at work. Like you said, the big downside is cost, and sometimes space. I really built this for someone that needs something low cost and simple to hook up. AC is a strange world.

This circuit is just about the same thing that goes inside a SCR, it is really just making the cost less by breaking it down and giving some more control.

I am working on the final version and am leaning towards a separate board rather than working with a shield. I personally love the shield idea, but I know it can strick some high voltage fears in people.

Here is the current UNTESTED code... let me know if you have any better ideas of doing this... I hope to test this tonight or tomorrow.

/*
  AC Light Control
  
  Ryan McLaughlin <ryanjmclaughlin@gmail.com>
  
  The hardware consists of an Triac to act as an A/C switch and 
  an opto-isolator to give us a zero-crossing reference.
  The software uses two interrupts to control dimming of the light.
  The first is a hardware interrupt to detect the zero-cross of
  the AC sine wave, the second is software based and always running 
  at 1/128 of the AC wave speed. After the zero-cross is detected 
  the function check to make sure the proper dimming level has been 
  reached and the light is turned on mid-wave, only providing 
  partial current and therefore dimming our AC load.
  
  Thanks to http://www.andrewkilpatrick.org/blog/?page_id=445 
    and http://www.hoelscher-hi.de/hendrik/english/dimmer.htm
   
 */
 
#include <TimerOne.h>           // Avaiable from http://www.arduino.cc/playground/Code/Timer1

volatile int i[4], j;               // Variable to use as a counter
volatile boolean zero_cross[4] = {0,0,0,0};  // Boolean to store a "switch" to tell us if we have crossed zero
int AC[4] = {4,5,6,7};          // Setup the AC output pins
int ACenable[4] = {1,1,1,1};    // Enable dimming for this output
int output[4] = {64,64,64,64};  // Create output vars for Dimming level (0-128)  0 = on, 128 = 0ff
int dim = 0;                    // Dimming level (0-128)  0 = on, 128 = 0ff
int freqStep = 65;              // Set the delay for the frequency of power (65 for 60Hz, 78 for 50Hz) per step (using 128 steps)
                                // freqStep may need some adjustment depending on your power the formula 
                                // you need to us is (500000/AC_freq)/NumSteps = freqStep
                                // You could also write a seperate function to determine the freq

void setup() {                                      // Begin setup
  pinMode(AC[0], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[1], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[2], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[3], OUTPUT);                           // Set the Triac pin as output

  attachInterrupt(0, zero_cross_detect, FALLING);   // Attach an Interupt to Pin 2 (interupt 0) for Zero Cross Detection
  Timer1.initialize(freqStep);                      // Initialize TimerOne library for the freq we need
  Timer1.attachInterrupt(output_check, freqStep);   // Use the TimerOne Library to attach an interrupt
                                                    // to the function we use to check to see if it is 
                                                    // the right time to fire the triac.  This function 
                                                    // will now run every freqStep in microseconds.   
  Serial.begin(9600);                                         
}                                                   // End setup
  
void zero_cross_detect() {                 // function to be fired at the zero crossing                           
    zero_cross[0] = 1;                     // set the boolean to true to tell our dimming function that a zero cross has occured
    zero_cross[1] = 1;
    zero_cross[2] = 1;
    zero_cross[3] = 1;
}                                          // End zero_cross_detect

void output_check() {                      // Function will fire the triac at the proper time

for( j=0; j<4; j++ ) {                     // Loop this function for each one of the outputs
  if(zero_cross[j]) {                      // First check to make sure the zero-cross has happened else do nothing
    if(i[j] >= output[j] && ACenable[j]) { // Check and see if i has accumilated to the dimming value we want
      digitalWrite(AC[j], HIGH);             // Fire the Triac mid-phase
      delayMicroseconds(2);                // Pause briefly to ensure the triac turned on
      digitalWrite(AC[j], LOW);              // Turn off the Triac gate (Triac will not turn off until next zero cross)  
      i[j]=0;                              // If we fired the triac, reset the counters
      zero_cross[j] = 0;                   // Reset the zero cross detection
    } else { 
      i[j]++;                              // if nothing is done incriment th counter
    }
  }                                       // End zero_cross check
}                                         // End each loop
}                                         // End output_check function

void loop() {                        // Main Loop
 
  output[0] = 75;                    // Set output values for dimming
  output[1] = 25;
  output[2] = 50;
  output[3] = 100;

  Serial.print("Output Values:   ");
  Serial.print(output[0]);
  Serial.print("\t");
  Serial.print(output[1]);
  Serial.print("\t");
  Serial.print(output[2]);
  Serial.print("\t");
  Serial.print(output[3]);
  Serial.print("\n");
}

ryanjmclaughlin,

Thanks for the pointer, I should have read the data sheet more carefully - you're right the info is there.

To clarify, it's 230-240V/50Hz, used in New Zealand, Australia and the Pacific.

I've done a little work with HV before but not a huge amount, I'd be installing on a separate board in a box isolated from the Arduino. More than happy to work together if you're interested.

Cheers,

hads

hads,

Let me know if you can do any testing. there may be a good resistor combination for both power systems. e-mail me an I can show you some of the designs for the next board. I am used to doing HV, but new at micros.

what is the voltage on the line for you? I know in US what we call 220V is actually 4-conductors... 120V, 120V, Neutral, GND. Is your power the same way? or 240V, Neutral, Ground?

ryanjmclaughlin,

Our 240V is 3 conductor, 240V, Neutral, Ground.

I'll drop you an email regarding testing.

Cheers,

hads

Another red herring (probably) for the person that wants to dim lighting with no drama.....
All the stores carry light dimmers - around 700 watts for around $10.
This is relatively attractive pricing. The potentiometer dimmer control would not be hard to automate with a smoothed analog output, I suspect!

Brian W

The components on this board are exactly what is in the hardware store dimmers... My current prototype... 800W * 4 channel ~$30

I would suggest trying to design the shield so that another of the same type could be stacked on top. That would give the user the ability to control 8 lights, in a 4 x 2 configuration (with 2 AC inputs).

Otherwise, I'll be definitely putting this on my "shields to buy" list!

Here is the modified and tested code, it works good! I just need to add an inductor to the board to get rid of some of the triac buzz.

/*
  AC Light Control
  
  Ryan McLaughlin <ryanjmclaughlin@gmail.com>
  
  The hardware consists of an Triac to act as an A/C switch and 
  an opto-isolator to give us a zero-crossing reference.
  The software uses two interrupts to control dimming of the light.
  The first is a hardware interrupt to detect the zero-cross of
  the AC sine wave, the second is software based and always running 
  at 1/128 of the AC wave speed. After the zero-cross is detected 
  the function check to make sure the proper dimming level has been 
  reached and the light is turned on mid-wave, only providing 
  partial current and therefore dimming our AC load.
  
  Thanks to http://www.andrewkilpatrick.org/blog/?page_id=445 
    and http://www.hoelscher-hi.de/hendrik/english/dimmer.htm
   
 */
 
#include <TimerOne.h>                        // Avaiable from http://www.arduino.cc/playground/Code/Timer1

volatile int i[4], j;                        // Variable to use as a counter
volatile boolean zero_cross[4] = {0,0,0,0};  // Boolean to store a "switch" to tell us if we have crossed zero
int AC[4] = {4,5,6,7};                       // Setup the AC output pins
int ACenable[4] = {1,1,1,1};                 // Enable dimming for this output
int output[4] = {0,0,0,0};                 // Create output vars for Dimming level (0-128)  0 = on, 128 = 0ff
int dim = 0;                                 // Dimming level (0-128)  0 = on, 128 = 0ff
int freqStep = 59;                           // Set the delay for the frequency of power (65 for 60Hz, 78 for 50Hz) per step (using 128 steps)
                                             // freqStep may need some adjustment depending on your power the formula 
                                             // you need to us is (500000/AC_freq)/NumSteps = freqStep
                                             // You could also write a seperate function to determine the freq

void setup() {                                      // Begin setup
  pinMode(AC[0], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[1], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[2], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[3], OUTPUT);                           // Set the Triac pin as output

  attachInterrupt(0, zero_cross_detect, FALLING);   // Attach an Interupt to Pin 2 (interupt 0) for Zero Cross Detection
  Timer1.initialize(freqStep);                      // Initialize TimerOne library for the freq we need
  Timer1.attachInterrupt(output_check, freqStep);   // Use the TimerOne Library to attach an interrupt
                                                    // to the function we use to check to see if it is 
                                                    // the right time to fire the triac.  This function 
                                                    // will now run every freqStep in microseconds.   
  Serial.begin(9600);                                         
}                                                   // End setup
  
void zero_cross_detect() {                 // function to be fired at the zero crossing                           
    zero_cross[0] = 1;                     // set the boolean to true to tell our dimming function that a zero cross has occured
    zero_cross[1] = 1;
    zero_cross[2] = 1;
    zero_cross[3] = 1;
}                                          // End zero_cross_detect

void output_check() {                        // Function will fire the triac at the proper time

  for( j=0; j<4; j++ ) {                     // Loop this function for each one of the outputs
    if(zero_cross[j]) {                      // First check to make sure the zero-cross has happened else do nothing
      if(i[j] >= output[j] && ACenable[j]) { // Check and see if i has accumilated to the dimming value we want
        digitalWrite(AC[j], HIGH);           // Fire the Triac mid-phase
        delayMicroseconds(2);                // Pause briefly to ensure the triac turned on
        digitalWrite(AC[j], LOW);            // Turn off the Triac gate (Triac will not turn off until next zero cross)  
        i[j]=0;                              // If we fired the triac, reset the counters
        zero_cross[j] = 0;                   // Reset the zero cross detection
      } else { 
        i[j]++;                              // if nothing is done incriment th counter
      }
    }                                        // End zero_cross check
  }                                          // End each loop
}                                            // End output_check function

void loop() {                        // Main Loop
  for( int k=0; k < 128; k++ ) {     // Slowly fade out all channels at the same time
    output[0] = k;
    output[1] = k;
    output[2] = k;
    output[3] = k;
    
    Serial.print("Output Level:\t"); // Print the current output level over serial
    Serial.println(output[0]);
    
    delay(200);                      // Delay after each step to slow down the fade
  }
}

How about boxing it so that only the arduino pins are accessible? You could possibly even pot the whole board so that the header pins stick out the bottom and the next-layer header sockets stick slightly out the top. Then one ac power cord sticks out the 'in' side and four ac power cords stick out the 'out side'. Nothing says shields have to be bare circuit boards.

Ryan,

OK, this is my first time ever offering a big code change to someone so I hope this works out. :slight_smile:

I took your latest posted code and modified it to fire the triacs based on the zero crossing moment plus some delay as calculated by the dimmer level and a dimmer resolution value (all in microseconds). This removed the software timer-based interrupt. I felt that statically dividing the line frequency would drift over time and the dimmer would slowly get lower or brighter, depending on the slight variance in the Arduino's clock and possible line frequency fluctuations. This change also allows support for almost any line frequency, even if it's changing radically and you don't have to think about it.

I'd really like it if one of the more seasoned programmers would take a look at this and make sure I didn't do anything obviously wrong (which I'm pretty sure I did). I know there is some issue with variables used in interrupt-triggered functions as needing to be declared as volatile or something. But I'm not sure when that need to be done.

Enjoy!

UNTESTED CODE!! (but it does compile)

/*
  AC Light Control
  
  Ryan McLaughlin <ryanjmclaughlin@gmail.com>
  
  The hardware consists of an Triac to act as an A/C switch and
  an opto-isolator to give us a zero-crossing reference.
  The software uses two interrupts to control dimming of the light.
  The first is a hardware interrupt to detect the zero-cross of
  the AC sine wave, the second is software based and always running
  at 1/128 of the AC wave speed. After the zero-cross is detected
  the function check to make sure the proper dimming level has been
  reached and the light is turned on mid-wave, only providing
  partial current and therefore dimming our AC load.
  
  Thanks to http://www.andrewkilpatrick.org/blog/?page_id=445
    and http://www.hoelscher-hi.de/hendrik/english/dimmer.htm
  
 */
 
/*
  Modified by Mark Chester <mark@chesterfamily.org>
  
  to use the AC line frequency (half-period) as a reference point 
  and fire the triacs based on that plus a dimmer delay value.
  I removed the second timer-based interrupt and replaced it with a
  means to reference the zero-crossing point as per interrupt 0.
  It also tracks the rollover of the internal microseconds counter
  to avoid the glitch that would arise about every 70 minutes otherwise.
  
  THIS CODE IS UNTESTED!!!
  Use at your own risk.

  But I would love to hear whether/how well this works!  :)
*/

#include <TimerOne.h>                        // Avaiable from http://www.arduino.cc/playground/Code/Timer1

unsigned long int ZeroXTime1=0;              // Timestamp in micros() of the latest zero crossing interrupt
unsigned long int ZeroXTime2=0;              // Timestamp in micros() of the previous zero crossing interrupt
int uRollover;                               // A value that can go negative when micros() rolls over
unsigned long int XTimePeriod;               // The calculated micros() between the last two zero crossings
int DimResolution = 128;                     // How many levels of dimming
unsigned int DimStep;                        // The number of micros() in each step of DimResolution
unsigned long int LastTriacFire;             // Timestamp in micros() of the last time the triacs were fired
unsigned long int NextTriacFire;             // Timestamp in micros() when it's OK to fire the triacs again.
boolean WaitForRollover = 0;                 // Used for when the triac fire delay calculates out to a number beyond the rollover value of micros()

volatile int i[4], j;                        // Variable to use as a counter
volatile boolean zero_cross = 0;             // Boolean to store a "switch" to tell us if we have crossed zero
int AC[4] = {4,5,6,7};                       // Setup the AC output pins
int ACenable[4] = {1,1,1,1};                 // Enable dimming for this output
int output[4] = {0,0,0,0};                   // Create output vars for Dimming level (0-128)  0 = on, 128 = 0ff
int DimLevel = 0;                            // Dimming level (high value = more dim, low value = more light)

void setup() {                                      // Begin setup
  pinMode(AC[0], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[1], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[2], OUTPUT);                           // Set the Triac pin as output
  pinMode(AC[3], OUTPUT);                           // Set the Triac pin as output

  attachInterrupt(0, zero_cross_detect, FALLING);   // Attach an Interupt to Pin 2 (interupt 0) for Zero Cross Detection
  Serial.begin(9600);                               
  delay(50);                                        // Give interrupt 0 some time to catch a few AC cycles
}                                                   // End setup
  
void zero_cross_detect() {                                   // function to be fired at the zero crossing
  ZeroXTime2 = ZeroXTime1;                                   // shift the current zero cross value to the previous
  ZeroXTime1 = micros();                                     // set the new current zero cross time in micros()
  if (ZeroXTime2 > ZeroXTime1){                              // Trap when micros() rolls over
    uRollover = ZeroXTime2 - 4294967295;                     // Subtract the rollover value to get a negative number
    XTimePeriod = ZeroXTime1 - uRollover;                    // And calc XTimePeriod from that
  }
  else {                                                     // Otherwise just calc as usual
    XTimePeriod = ZeroXTime1 - ZeroXTime2;                   // Calculate the time since the last zero crossing
  }
  DimStep = XTimePeriod / DimResolution;                     // Determine how many micros() are in each step of dimming level
  if ( (ZeroXTime1 + (DimLevel * DimStep)) > 4294967295 ) {  // If this pushes the value of NextTriacFire past the micros() rollover
    WaitForRollover = 1;                                     // Then set a flag to watch for.
  }
  NextTriacFire = ZeroXTime1 + (DimLevel * DimStep);         // Calc the next triac fire time
  zero_cross = 1;                                            // set the boolean to true to tell our dimming function that a zero cross has occured
}                                                            // End zero_cross_detect

void loop() {                                                // Main Loop

// Set DimLevel in some fashion. This just sets it to random values.
  randomSeed(analogRead(0));                                 // Initialize the randomizer EVERY TIME (this is not a good thing to do)
  DimLevel = random(DimResolution);                          // Pull a number out of the hat that is <= DimResolution
  Serial.print("Output Level:\t");                           // Print the current output level over serial
  Serial.println(DimLevel);

// DimLevel is used to set NextTriacFire in zero_cross_detect

// NextTriacFire is used here
  if ( WaitForRollover && ( micros() < NextTriacFire ) ) {
    WaitForRollover = 0;
  }
  if ( zero_cross && !WaitForRollover ) {                    // Did we cross zero?
    if ( micros() >= NextTriacFire ) {                       // Check to see if it's time to fire the triac
      for( byte j=0; j<4; j++ ) {                            // For each one of the outputs
        digitalWrite(AC[j], HIGH);                           // Fire the Triac mid-phase
        delayMicroseconds(2);                                // Pause briefly to ensure the triac turned on
        digitalWrite(AC[j], LOW);                            // Turn off the Triac gate (Triac will not turn off until next zero cross)
        zero_cross = 0;                                      // Reset the zero cross detection
      }
    }
  }
}

It occurred to me after my last post that you may not have wanted anyone's help on the code. I apologize for stealing your fun if that's the case.

I did have a chance last night to build a test harness and test the code I posted. For the most part it works. I had to make a few minor changes to stabilize the timings a little, but I was able to get a 2us pulse at pretty much any resolution and dim level, and any reasonable line frequency. Let me know if you would like the modified code and I'll be glad to post it again.

Cheers!

Not at all! Thanks for the help! I am not really too good on the software side, so I am always looking for help.

I looked at the code for a bit, but have not been able to test it yet. How would this work when you want to put different dim levels on 4 different channels? Thanks for the input!

I boiled it down to one channel in order to figure out the timing. But to expand it out to N channels is just a matter of establishing the control/input for each and then duplicating some of the math and certain variables and running some loops. I'm going to play with it some more tonight and I'll see where I can get with multi-channel stuff.

Ironically, I'm not so good on the software part either, which is why I started messing with your code. :wink: You had some interrupt stuff I wanted to become familiar with and I was intrigued by how you handled the zero-crossing requirements of triacs. For my current Arduino project, I considered using triacs to control the output of a 3-phase alternator but couldn't wrap my head around how they work. But after seeing the opto-trigger chip you're using and the IC that senses the line signal - man that's a life saver - I now understand how to handle them! So maybe I'll reconsider them as a solution.

Looks quite useful. I wonder if the danger is really that great.

Looks quite useful. I wonder if the danger is really that great.

There you have it.
Someone who does not understand the danger thinks it "looks quite useful".

110v isn't quite as lethal as the 240v single phase mains (as used conventionally in much of the world outside North America).
In the UK, pro building site power tools etc are actually stepped down to 110v to reduce the risk.
But even so, 110v is not 'playful' stuff.
And 240 volts is deadly serious.

Good design keeps people and control logic well isolated from power.
This shield concept flies directly in the face of that.
As the original poster put it: -

I understand what you mean about doing a seperate box, however then there is no point of a shield at all, the only actual logic on the board is the arduino pins to drive the opto-couplers

So the only point of doing this as a shield is to deliberately bring power and logic (and by implication power and people) closer together. :-?

Much much much better to parcel up ALL the mains stuff in its own box with a mains inlet and outlet(s), plus neat connectors to take logic-level control inputs. And inside the box, on their way direct to the opto-isolators, keep those logic-level conductors physically isolated from the mains electric stuff.
A 'mains is live' neon indicator on the box would be good, too. And an isolation switch certainly wouldn't be a bad idea.

Then you have made yourself a general-purpose mains-controlling box for your projects.
With minimal risk of qualifying for an internet Darwin Award.

However if you were being sensible about it, you'd just buy SSRs to go inside the box, rather than building your own.
They are actually pretty cheap on eBay.
Well, I reckon as little as US $8 including delivery is pretty cheap for a nominal 25 amp unit ... (I went $10 for the Fotek one) ... the heat sink is actually more expensive than the SSR - but for switching only a few amps the heat sink isn't needed.

And for the truly prudent, its worth pointing out that you'd be even safer if you supplied the mains power to your project through an RCD (sometimes called ELCB) safety breaker plug.
Here's one UK example from B&Q http://nextday.diy.com/app/jsp/product/productPage.jsp?productId=16930
You'd probably never notice such a device, until it saves your life.

PS - People who write

I've been looking at doing some lighting control myself and have been researching building something like this.
...
I agree an SSR would be ideal, except in this particular instance I'd like the ability to dim lighting.

do not inspire me with confidence in their research!
I'd strongly advise such people NOT to "build something like this".
Especially when an SSR should be ideal for dimming lighting.
This thing is basically a homebrew quad SSR, but 100% exposed and bringing mains electricity intimately close to the Arduino (and anyone 'interacting' with the Arduino).

Is there a danger? Yes.

I'm I worried? No. Partially, because in previous jobs I have worked with 240V 400A 3-phase power while it is live. So I know how to work with 120V. Should the general hobbyist that has never worked with AC before use this, maybe not.

If you have ever replaced a light switch is is no more dangerous. This being a PROTOTYPE! Shows me the concerns, and peoples' opinion. The final version will obviously be very different. An hopefully add some more features.

that_chap, Please don't discourage people from doing the research and trying to build their own designs and tinker. That is the whole point of the Arduino platform. I love input, but hate when people try to crush ideas just because they do not like it.

I love input, but hate when people try to crush ideas just because they do not like it.

He wasn't criticizing because he didn't like it, he was criticizing because it is potentially lethal. Of course you are welcome to play around with live 120/240 volts if you feel so inclined, but IMO suggesting that it's safe for people to tinker with live AC power is not responsible.

Tinkering is about having fun and exploring possibilities – it is an essential element of Arduino. But it has no place where potentially lethal voltages can be exposed.

Should the general hobbyist that has never worked with AC before use this? absolutly not!

Is there a danger? Yes.

I'm I worried? No. ...

Ryamjmclaughlan, in those few words you have crystallised my concerns perfectly.

PS - People who write
...
do not inspire me with confidence in their research!
I'd strongly advise such people NOT to "build something like this".
Especially when an SSR should be ideal for dimming lighting.
This thing is basically a homebrew quad SSR, but 100% exposed and bringing mains electricity intimately close to the Arduino (and anyone 'interacting' with the Arduino).

Thanks for mentioning me. Actually I do quite a lot of research. As you imply I mustn't be very good at it since I've not found anything remotely close to an example of using a solid state relay to do Arduino controlled dimming.