Go Down

Topic: Arduino AC Power Shield! (Read 29867 times) previous topic - next topic

hads

If you have the time, I'm curious what would need to be changed for this to work with 240V/50Hz. Are you sill using the same schematic from your other post?

I've been looking at doing some lighting control myself and have been researching building something like this.

Cheers,

hads

betwys

I know you are not directing this query to me, but using ONE Arduino digital output to drive a SEPERATE bolt down SSR means you buy just one thing - a 120 volt or a 240 volt SSR at whatever rating you need: 10A, 25A  or 40A

This would be a safe, no brainer approach except for one small detail:
opto-isolated zero crossing SSRs don't come cheap.
Sadly.

Brian Whatcott

hads

I agree an SSR would be ideal, except in this particular instance I'd like the ability to dim lighting.

ryanjmclaughlin

hads,

240V/50Hz...  is that 120V on 2 legs?  I am not that familiar with that power.

Check out the data sheet for the opto-triac drivers.  http://www.fairchildsemi.com/ds/MO%2FMOC3020M.pdf it has some 240V examples.  Basically the same setup with some different resistor values between the opto-coupler and triac.  The zero-cross can stay the same, just ensure you adjust the resistor value for the rating of the H11AA1 maybe 50k?

Remember, if you want to switch high current loads with this, your PCB needs to have large traces and a heavy copper layer to handle the current, even though the triac may be rated for 12A make sure the interconnects are as well!

ryanjmclaughlin

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.

ryanjmclaughlin

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.

Code: [Select]
/*
 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");
}


hads

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

ryanjmclaughlin

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?

hads

ryanjmclaughlin,

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

I'll drop you an email regarding testing.

Cheers,

hads

betwys

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

ryanjmclaughlin

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

DarkStar

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!
Join the fun! - DorkbotPDX & Make:PDX (Portland, Oregon)

ryanjmclaughlin

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.

Code: [Select]
/*
 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
 }
}


audin

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.

koyaanisqatsi

Ryan,

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

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)
Code: [Select]
/*
 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
     }
   }
 }
}
What about elevensies? Luncheon? Afternoon tea? Dinner? Supper?

Go Up