Go Down

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


Koya - As far as verifying the power curve, it is hard. Though most normal multimeters with True RMS splashed across the front are not really true rms they may still be designed not to be completely useless for non sine waves. At least our wave form is symmetrical about 0. It might be worth just putting a meter across the output voltage and see if the map makes it more linear. Perhaps use different code so that you can step up the dim value in a controlled way rather than using analogue read.

Also remember it will not be accurate down to the single dimres step because the map has the same resolution (255) on both input and output, but over say 5 or 10 steps it would even out to be pretty equal.

Btw here is what seems to be true true rms meter:

Only 2300USD :)

I am pretty confident in the math - the starting point in my first post I was clearly getting the area under the curve graphed (which is the power) and I was able to plug numbers from my solution back into that page and get the right answers.

Here are the steps I went through, my working if you can call it that: http://caff.cx/arduino.cc_1236998971/

If there is a problem I think it will be in implementation not in the formula.

Bluefish - I would be curious to see what your kill-a-watt makes of it - I can sort of see the curve we are trying to straighten in your other post of the pre corrected power output.


Hi caff and koyaanisqatsi,

After I tested your revised code, I didn't find any big different between old and revised codes from my oscilloscope. From my observation, the old code is more stable than the revised code, because the AC voltage waveform sways occasionally when the revised code is using. Thank you very much, koyaanisqatsi. You did a very good job.

For the calculation of AC period, this is depending on how much accuracy you want to achieve.  It is different between 95 and 100% accuracy in math. But it is not a lot of different for non-time-sensitive applications (second or minute level), like home furnace temperature control.
For variable timer, sampling timer and AC waveforms (voltage and current) are variable by time, what factor you want to use for a reference? This will let AC phase control become a complex problem. I believe you can figure it out in math, and phase control can becomes very accuracy. (micro second level ?) But, is it worth for non-time-sensitive applications? The old code using timer from hardware as a reference calculates the average period of AC. Although the period of AC is not 100% accuracy, it is enough to do a lot of applications.

To calculate AC power output, this is a statistic problem. AC voltage and current values are running anytime. I use real time voltage multiply by real time current to obtain real time AC power output for resistor only system, like DC circuit (Power = Voltage * Current).  The difference is that both AC real time voltage and current values are including their phase factors. Therefore, the average of AC power output per unit time is RMS power per unit time.


Jul 23, 2009, 10:09 pm Last Edit: Jul 23, 2009, 10:15 pm by bluefish Reason: 1
To Caff,

After I did more test with different loads, you are right. Your revised code can work more accurate than the old one.

Output results of both codes are not different for low load applications (I used 100w bulb) or working with a constant trigger value. When high load (300w or higher) is applied, the old code becomes unstable (the code shifts its trigger value between two random values per 3 second or fixes in a constant) after I changed the value of analogread (from min to max or max to min) several times (3-5 times). The shift period changed when I changed the value of Periodsync. But the revised code doesn't have any problem at the same condition.

For swaying waveform, I checked my circuit again and found that an output wire was loose a little bit. Sorry, this is my mistake.

I would like to say thank you. You let the code becomes more perfect.


PS: I use Labview to control AC output. Therefore, the value of analogread is sent from Labview. It isn't from a potentiometer.


Hey guys nice work on the project.

Does anyone have a schematic for this? Im thinking about using something like this for some lightning effects and would like to get a general idea of what id be getting into.



Hi Ill Mill,
The other guys might have more to tell you about this, there are pictures of the power shield which controls four outputs at the start of this topic. The application note PDF linked to earlier in this topic has almost exactly the circuit you need for one output, you would simply replace the 555 section with the Arduino http://www.fairchildsemi.com/an/AN/AN-3006.pdf.

Also see the posts by bluefish in his AC box topic http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1240690030 include some circuit diagrams, you'll need one zero crossing detector and a triac driver for each output.

You probably shouldn't take on something like this unless you feel confident, otherwise you could hassle ryanjmclaughlin to see if he still plans to sell the AC shield prebuilt or as some kind of kit.


Hi bluefish,

I understand what you mean about the big jumps you will see on the scope with the new code. The reason is the AC waveform has a low voltage at the edges so to get the same amount of change you need to change the timing by a lot. We are also mapping from 255 values to 255 values so for example the step from zero to one in the power setting will mean a jump from 255 down to 245 in the delay value. If you saw it flickering between two values by itself that will be because the analog read was getting slightly different values each time and jumping between two power values. If you are near the start or the end of the range it would look like large jumps even though it only represents small jumps in the amount of power.

This does go back to the loss of resolution because the map has the same resolution on its input and output some output values are no longer possible because they don't match any of the input values. Overall it should still be more accurate i.e. if you set the dimmer value to 25% you should definatly be getting much closer to 25% power in a heater element than with code which does not try to correct for the curve. Did you make a curve with the kill-a-watt yet?

I actually think when I make my hardware I will probably start the code from scratch because I would like try to control the timer directly so that an interrupt happens at exactly the time when I want to trigger the triac. This will mean my map can have much higher resolution on the output time delay value, it also means not triggering the interrupt hundreds of times per cycle. I have not studied how the timers worked yet but I assume this should be possible especially because I only want to control one triac rather than four.

Here is another small improvement to the code. It is designed to ensure that when you set the power to either zero or full you will truly get zero or full activation of the triac even if there are small discrepancies in the timing. If zero power is selected then the trigger pulse will not be sent, if 100% power is set it will hold the triac output pin high.

This replaces the fire_triacs() function:
Code: [Select]

void fire_triac(int TriacNum) {
 if ( FireTriac[TriacNum] == DimStepCounter ) {              // Is it time to fire?
   if(FireTriac[TriacNum]!=DimRes)                           //When the power output is 0% never turn the triac on
     digitalWrite(TriacPin[TriacNum], HIGH);                 // Fire the Triac mid-phase

   if(FireTriac[TriacNum]!=DimRes && FireTriac[TriacNum]!=0) //Only bother to add a timing delay when both digitalWrites are active

   if(FireTriac[TriacNum]!=0)                                //When the power output is 100% never turn the triac off
     digitalWrite(TriacPin[TriacNum], LOW);                  // Turn off the Triac gate (Triac will not turn off until next zero cross)


void fire_triacs() {                                   // Called every DimStep (Timer1 interrupt, checks FireTriac[n] and fires if it's time

 for(int i=0; i<4; i++)                               // Fire each of the triacs

 DimStepCounter++;                                    // This counter increments every time fire_triacs runs

I also have an idea that if you wanted to reduce heating of the triac at 100% power. The highest heat dissipation of the triac will occur when it is 100% on. You could bypass it with a relay which is also activated by the triac trigger pin. Using the the code above the relay could be set up so that it only activates when the line is held high and ignores the small trigger pulses when the triac is partially activated


I'm going out on a limb here because I can't remember what state this code is in and my Arduino is fried so I can't test it.  But last time I tested this code it was very smooth.  I remember the previous version I posted was very jumpy.  This is the code (or a close version of it) I'm using in my YouTube video http://www.youtube.com/watch?v=ZO9kRJdj6gg.

So here it is.  I'm pretty sure this works OK.  But there may be a bug in it regarding the millis() function being used in ISRs.

Try modifying this with the sine correction and see how it behaves.


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.

// General
volatile unsigned long int ZeroXTime1 = 0;             // Timestamp in micros() of the latest zero crossing interrupt
volatile unsigned long int ZeroXTime2 = 0;             // Timestamp in micros() of the previous zero crossing interrupt
volatile unsigned long int XTimePeriod;                // The calculated micros() between the last two zero crossings
byte TriacFireWidth = 2;                               // How many microseconds to leave the triac trigger high
unsigned int DimChangeDelay = 500;                     // How many millis() between changes of dimmer level
unsigned long int DimChangeTime = 0;                   // Timestamp in millis() when the dimmer was changed

// Channel specific
# define Channels 4                                    // How many dimmer channels (triacs) are there?
unsigned long int DimLevel[Channels];                  // Dimming level (high value = more dim, low value = more light)
unsigned long int DimDelay[Channels];         // How many micros() to wait after zero cross to fire the triacs
unsigned long int NextTriacFire[Channels];    // Timestamp in micros() when it's OK to fire the triacs again.
volatile boolean zero_cross[Channels];                 // Boolean to store a "switch" to tell us if we have crossed zero

// Associate pins to each channel (triac)
byte TriacPin[Channels] = {4,5,6,7};

void setup() {                                         // Begin setup
 for ( byte c=0; c<Channels; c++ ) {                  // Loop through and set pin modes
   pinMode(TriacPin[c], 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
 delay(50);                                           // Give the interrupt time to capture 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()
 XTimePeriod = ZeroXTime1 - ZeroXTime2;               // Calculate the time since the last zero crossing
 for ( byte c=0; c<Channels; c++ ) {
   DimDelay[c] = XTimePeriod / DimLevel[c];          // Calc how long to wait after zero cross to fire the triacs
   NextTriacFire[c] = ZeroXTime1 + DimDelay[c];      // Calc the next triac fire time
   zero_cross[c] = 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.
 DimLevel[0] = analogRead(0);                      // For testing, we're just reading an analog input
 DimLevel[1] = analogRead(0);                      // For testing, we're just reading an analog input
 DimLevel[2] = analogRead(0);                      // For testing, we're just reading an analog input
 DimLevel[3] = analogRead(0);                      // For testing, we're just reading an analog input

// DimLevel is used to set NextTriacFire in zero_cross_detect()
 for ( byte d=0; d<Channels; d++ ) {                                 // Loop through each channel
   if ( zero_cross[d] ) {
     if ( micros() >= NextTriacFire[d] ) {                         // Check to see if it's time to fire the triac
       digitalWrite(TriacPin[d], HIGH);                            // Fire the Triac mid-phase
       delayMicroseconds(TriacFireWidth);                          // Pause briefly to ensure the triac turned on
       digitalWrite(TriacPin[d], LOW);                             // Turn off the Triac gate (Triac will not turn off until next zero cross)
       NextTriacFire[d] = NextTriacFire[d] + XTimePeriod;
       zero_cross[d] = 0;                                          // Reset the zero cross detection  


Whats the status of this?

I'd like a small box with a Male Female connector on it, with two pins for Ground and Signal, where Signal is 0 for Off, and +5 for On.

Do you have anything like this?
I guess I could build it, do I need anything other than a RELAY for this?


That's what a Solid State Relay (SSR) is.


Jul 28, 2009, 07:44 pm Last Edit: Jul 28, 2009, 07:59 pm by bluefish Reason: 1
To Koyaanisqatsi,

Your code works well. That's why I can't find any difference when I tried your code and the revised code first time. After more detail tests including using high load tests (about 1000 watt) and quickly changed phase trigger value several times from the lowest to the highest or reverse, I lost phase control by using you code. That is because I did an extreme test for both codes. I think the code has a bug about analog read pin when power output is the maximum or minimum output. For normal test, it works very well. You did a good job. ;D

I will try you new code later and then let you know what I get.

To Caff

I agree your argument. The bug comes from analog read pin. I also found that the output suddenly becomes the maximum when the phase value is low than 20 by using the revised code. The output becomes unstable between 20 and 40. The difference between both codes is that the phase will be under control when I increase phase trigger value a little (> 20). But the old code can't be controlled unless I restart Arduino. According to my test, the most stable and lowest value is 40 for your revised code.

Thank you for your advice about maximum output ideal. For my application, I can't use the maximum output. It will melt my furnace immediately. One more thing, which I want to remind everyone on this project, is that power output always is the maximum before Arduino finds the average period first time when you start Arduino. After Arduino gets AC period and then starts to send trigger signal, the output is under control. For some cases, this short time is enough to burn out something. For me, I will add a switch before my furnace to avoid the maximum output.

For your project, I know you don't want to turn on you furnace continuous. You only want to turn it on a couple of seconds or even one AC cycle. This is an interesting AC control project. :)

For Kill a Watt, it isn't ready to be used.  I can't get correct curves from it. :'(

I have connected it with labview through Arduino. I can watch voltage, current and watt waveforms directly in real time on my computer. But the problem is that voltage value is not correct on computer. Basically, AC voltage is always variable when we turn on or off some high load electrical equipments on the same loop, like microwave oven, blender, hair dryer. I can watch the correct voltage value directly from kill a watt, which I had checked it with a voltage meter. But I always get wrong values on my computer when voltage value is changed. For example, the voltage value (ex: 115->110v) will decrease when I turn on microwave oven on the same loop. Kill a watt shows this change correct, but my computer always obtains higher values (115->120v). Oppositely, the voltage (110->115v) value increases. I get lower value (120->115v) on my computer. There should be a math function on kill a watt to let it show a corrected value. I read its data directly from its chip. Therefore, the voltage value is not correct.

I think the analog input always reads slight different values every time, like AC phase control, and I need to add a correct function. I read ladyada's project.  http://www.ladyada.net/make/tweetawatt/ She sets a contact for voltage (Vpp is 170). I will try it when I have free time.

Have a nice day


Hi my friends,

Due to my circuit was short circuit when I tried to add a switch on AC line. :-[ Everything is ok without triac. I took some time to redesign my circuit. Now, the circuit becomes safer than before. But I need to take extra time to do stable test before I test two codes for you.

To koyaanisqatsi
Before the circuit was short circuit, I put your new code on my Arduino. I observed this code can't work. My lamp is like a candle in the wind. The bright of my lamp always becomes dark or bright randomly. I guess that errors come from new timer.  Oppositely, your old code still works well under the same condition. I will try it again when I finish my stable test.

To Caff
I didn't have time to try your new code due to my circuit death. I will try it later.



Any new stuff happened on the board production line? Or has everyone being electrocuted? :D

It looks like an interesting project to me - any chance of a 230V UK version? I would like use this to control the power on my pc...



It would not take much to make the board compatible with 230V line (I think it already is actually)  But you would be better off using a regular switch or relay for a computer.  If you feed dimmed power into a computer power supply, you risk damaging the computer.  At best it will heat up the power supply more than normal.


That looks awesome. I could think of a number of uses for that, such as lighting automation.


If anyone's looking for a quick and slightly more newbie-friendly way of dimming mains power lighting, check out the light dimmer kit from Velleman:


(available from Maplin in UK)

Works on 110-125V or 230-240V, 50/60Hz so should be universal.

Rated 750W @ 230V or 375W @ 110V.

You give it a single control voltage which varies the voltage output.  I guess this might work OK with a PWM output (possibly with a smoothing capacitor on it), otherwise a simple R-2R ladder should do the trick.

The input voltage is meant to be 0-12V but will work fine on 0-5V if you adjust the threshold voltage.

The control input is opto-isolated.  The schematic is included in the PDF manual linked from the web page, if you want to use it as inspiration.

Go Up