Yummy's DIY Burst fire controller project

If it plugs in, post it here.

Moderator: Site Moderator

kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

I have tested the controller with my 2600 W Boiler and it worked as intended.
I had no flickering lights in the house nor other side effects with this 2600 W power.

However my existing Power meters indication started jumping around with power settings below 50%. This power meter can properly measure the distorted waveforms from usual SSR power controllers, but it gives up with burstfire. It seems that the integration period of the meter is just too short to deal with the 2 seconds long power pattern of the burst fire.
I could easily tear the Arduino controller apart and return the to my existing SSR controller without burst fire feature and treat this build as an interesting exercise.
However, the Arduino power controller opens the door for implementing additional features.
Automatic heating power reduction, shortly before the boiler starts producing vapor is one example.
So I decided to keep that burstfire power controller. That decision requires me to build a power meter that is able to deal with the burstfire pattern.
No surprise - Yummy has already developed one. The next project on my todo list.

A minute ago, I tried to control one of my air ventilators with burstfire.
Normal SSR controllers will not be very good in changing the speed of those ventilators. At low power settings, they just stop movement.
It is different with burstfire. The ventilator rotates slowly even with 1% power setting. Looks a bit weird because with very low speed, it moves in steps. Interesting.
Certainly useless, nevertheless interesting.
User avatar
Yummyrum
Global moderator
Posts: 8641
Joined: Sat Jul 06, 2013 2:23 am
Location: Fraser Coast QLD Aussie

Re: Yummy's DIY Burst fire controller project

Post by Yummyrum »

kennstminet wrote: Wed Jul 03, 2024 5:34 am I have tested the controller with my 2600 W Boiler and it worked as intended.
I had no flickering lights in the house nor other side effects with this 2600 W power.
That's great to hear ....and I'm very happy you have no light flickers.
kennstminet wrote: Wed Jul 03, 2024 5:34 am However, the Arduino power controller opens the door for implementing additional features.
Automatic heating power reduction, shortly before the boiler starts producing vapor is one example.
Absolutely :thumbup: ,
Once you can program the "knob" the worlds your Oyster :ebiggrin:
kennstminet wrote: Wed Jul 03, 2024 5:34 am No surprise - Yummy has already developed one. The next project on my todo list.
Just remember that that Meter will only work at this stage on 0-100% where the Frame rate is 2 seconds .
It would require a 20 second frame rate to work on 0-1000 , but the loop counter seems to be a signed integer with a max of 32,767 so it might need a total re-Jig to work . I also wonder if 20 seconds is too long for an update . Regardless , it works on Aubers with 0-100%
kennstminet wrote: Wed Jul 03, 2024 5:34 am I tried to control one of my air ventilators with burstfire.
Normal SSR controllers will not be very good in changing the speed of those ventilators. At low power settings, they just stop movement.
It is different with burstfire. The ventilator rotates slowly even with 1% power setting. Looks a bit weird because with very low speed, it moves in steps. Interesting.
Certainly useless, nevertheless interesting.
That is cool :thumbup:
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Yummy

I'm trying to synchronize the TRMS power meter with the burstfire controller.
For this reason, I would like to set an unused digital output of the controller to high when the 2 secs period starts and reset to zero when the period has finished.
Could you suggest where I could insert code for this function?
User avatar
Yummyrum
Global moderator
Posts: 8641
Joined: Sat Jul 06, 2013 2:23 am
Location: Fraser Coast QLD Aussie

Re: Yummy's DIY Burst fire controller project

Post by Yummyrum »

A little preoccupied at work ATM , but on my mind .
Will get back to you soon Kenn

Initial thoughts are the reseting of the frame counter ( as the Frame counter is counting 100 cycles ) and it would need to be as close to absolute Zero cross point as possible ( for accurate TRMS measure of a full 2 Cycles )

I think your Optocoupler had that point nailed . My half wave rectified is not near the crossing point so interrupt timing , although it works for the controller, is not aligned for TRMS
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Never mind. I will test your hint with the framecounter in the meanwhile.

I have added two lines to the ISR. It need to be tested, haven't done that yet.

Code: Select all

//########Interrupt Service Routine for the zerocrossing detector ##############
ISR (PCINT0_vect) {         // ISR for pin change on port B (Jump to interupt code when Zerocrossing detected )
	if (digitalRead(zerocross) == true){
	return; // is it a rising edge, then do nothing and returnto loop
	}
// only falling edges shall trigger the following steps
//Turn cycle on or off as determined by previous ISR calculation, triggered by the previous falling zerocrossing
	digitalWrite(ledPin, setoutput);

//Calculate state of "setoutput" for use after the next falling zerocrossing.
//This state will be based on the Patterns calculated in Main loop, using the desired percentage of power.
	if (Frame_counter >= (Maximum+1)) {
		digitalWrite(syncPin, 1); // set sync signal to 1 when frame counter starts
    Frame_counter = 1;
		PttnCnt_1st = 0;
		PttnCnt_2nd = 0;
		PttnCnt_3rd = 0;
		PttnCnt_4th = 0;
		PttnCnt_5th = 0;
		setoutput = false;
	}

	Frame_counter++;
	PttnCnt_1st++;                      // inc Pttn_1st counter

  if (Frame_counter = Maximum ){digitalWrite(syncPin, 0);} // set sync signal back to 0 when counter is full

kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

The synchronization works.
It seems that the TRMS power meter indication is more stable when synchronized. I will give more details in the power meter topic.
I noticed that the burstfirecontroller has an idle period of 40ms between two 2 sec. periods. However, I have not analyzed what exactly is causing that.


Now for something completely different ......
The controller could work as a real power regulator, if the TRMS power meter would be monitoring the voltage changes and telling the controller to increase or decrease the percent setting accordingly.
Will take a while. There is still some work in progress with the power meter.
quadra
Swill Maker
Posts: 372
Joined: Mon Oct 11, 2021 11:53 am

Re: Yummy's DIY Burst fire controller project

Post by quadra »

That would be really useful!
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Such power regulator would have a limitation.
The burstfirecontroller (nor any controller) cannot amplify the mains voltage, it can only reduce it.
If the mains voltage at a certain location would vary between 210V and 250V, the regulator would keep the voltage limited to 210V and prevent it from increasing. This would keep the voltage stable (and the power stable), however, at the level of the lowest expected voltage.
If the mains voltage would sink below that expected value, the regulator could not amplify it. The voltage would be lower, resulting in a lower power.
Not sure if this makes sense to a "non electrical" person.
User avatar
Yummyrum
Global moderator
Posts: 8641
Joined: Sat Jul 06, 2013 2:23 am
Location: Fraser Coast QLD Aussie

Re: Yummy's DIY Burst fire controller project

Post by Yummyrum »

I agree that syncing the power meter should give better results . Glad to hear it worked and is more stable .

Not sure about the missing 40ms ? Can’t say I noticed that .

You are correct about controller not being able to create a higher voltage . I’d hazard a guess that most folk would only be interested in “Regulated” power during a spirit run during which time most are running at quite a bit less than full power . So the Regulator should in most cases be able to supply a stable power even at the minimum dip in Mains supply Voltage .
And obviously when Stripping you just turn it up as much as it will give you .

All sorts of potential here to warn operator how close they are getting to the maximum available power .

Good work Kenn :thumbup:
User avatar
shadylane
Master of Distillation
Posts: 11274
Joined: Sat Oct 27, 2007 11:54 pm
Location: Hiding In the Boiler room of the Insane asylum

Re: Yummy's DIY Burst fire controller project

Post by shadylane »

kennstminet wrote: Fri Jul 26, 2024 11:39 am
The burstfirecontroller (nor any controller) cannot amplify the mains voltage, it can only reduce it.
Good point, if higher voltage is needed, then a variac becomes a logical option.
It's based on an autotransformer, so the output voltage can be higher than the input.
Just like every controller, a small fan makes a big difference. :lol:
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Yummyrum wrote: Fri Jul 26, 2024 2:53 pm Not sure about the missing 40ms ? Can’t say I noticed that .
I will look a bit deeper into it and let you know. It is not a problem. I just made the observation.
shadylane wrote: Fri Jul 26, 2024 3:54 pm ..... if higher voltage is needed, then a variac becomes a logical option.
How much power could the variac handle, you are thinking of?
I own a very old and very large one from the early days of color TV repairs and it's limited to 1500Watts.
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Yummy
The 40 millisecs is a non issue. It is just related to my incorrect implementation of the sync signal.
Will report details once it is fixed.
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

I guess Ivedunnit.

Here is my extension of Yummy's project for the Arduino R3 with added Power display, mains voltage display and power stabilization function.

A toggle switch is needed to turn the stabilization function on and if. It provides Ground to digital pin 6 to deactivate stabilization, or is open circuit to turn it on.

I have used that OLED Display and connected it for the SPI bus which works faster than the I2C interface:
https://www.ebay.de/itm/285819329121?mk ... media=COPY

This is how the displayed data looks like:

With stabilization switched on
IMG_3442.JPG
With stabilization switched off
IMG_3445.JPG
I used a small transformer with bridge rectifier as a voltage sensor for the mains voltage.
mains_voltage_sensor.jpg
Here is a sketch of the transformerless zerocrossing detector I'm using.
zerocrossing_detector.jpeg
zerocrossing_detector.jpeg (18.91 KiB) Viewed 1970 times
Here is the .ino file. I have added a .pdf extension to allow uploading to HD. Please remove the .pdf ending after downloding to mak it a .ino file again.
Burstfire_5_SPI_Stab_4.ino.pdf
(17.06 KiB) Downloaded 39 times
This is how the code looks like:

Code: Select all

/*########################################################################################

The sketch is based on the one created by Yummyrum and posted at HD 
June 25th, 2025 https://homedistiller.org/forum/viewtopic.php?p=7787168#p7787168
and is modified by kennstminet August 2024 filename Burstfire_5_Stab_4.ino

- Removed LCD code, added code for use of an 1.3 inch OLED display (SH1106) with SPI interface.
  I had tested with a similar OLED with I2C interface and found that I2C is too slow and it was disturbing the burstfire timing.
- Introduced a transformerless voltage sensor for detecting the mains voltage zerocrossing
- Added an optional synchronization output to allow syncing the timing with Yummyrums TRMS power meter.
  I found that such sycronization is not giving appreciable benefits. I left the code active, in case somebody needs it.
  The sync output is set to LOW at start of the Frame_counter and returns to HIGH 100 millisecs before the Frame_counter is full
- Removed the code for writing the pattern to the SSR from the ISR and made a regular function for it.
  Now, the ISR just sets a variable at zerocrossing and then returns to loop to avoid blocking the microcontroller for full 2 seconds.
- Added the "volatile" attribute to the variable used in the ISR
- Added code for measuring the current mains voltage (from the wall outlet). An external voltage sensor is needed for translating the 
  mains AC voltage to a 0 to 5VDC signal to the respective analog input of the arduino. 
- Added code to provide a means of power stabilization. It compares the actual mains voltage with a preset 
  "lowest expected mains voltage" and reduces the selected power accordingly if the actual mains voltage is higher than the lower limit.
  The max. available stabilized power is therefor lower than the power in unstabilized mode.
  However, it will remain stable, when the mains voltage is variing as long it is higher than the lowr limit.
- Added an digital input for a external switch to turn on and off the stabilization function.






##########################################################################################*/

#include <Arduino.h>        // I have no idea why this library is used here, so left it
#include <U8x8lib.h>        // library for the OLED
#include <SPI.h>            // for the OLED SPI interface
#include <math.h>           // for mathematical rounding function "roundf()2
#include "elapsedMillis.h"  // for creating a timer for the measurement duration

U8X8_SH1106_128X64_NONAME_4W_HW_SPI u8x8(/* cs=*/10, /* dc=*/9, /* reset=*/8);  //creates an OLED object "u8x8"
elapsedMillis timeElapsed1;                                                     // timer object for reading the analog inputs
elapsedMillis timeElapsed2;                                                     // timer object for printing data

unsigned int interval1{ 10 };  // Timer interval for reading the analog inputs
unsigned int interval2{ 10 };  // Timer interval for printing data

const float mainsVoltage_low{ 210 };  // the lowest voltage, the stabilizer should be able to compensate
const float elementResistance{ 23 };  // Constant holding the resistance of the heating element in Ohms.
const float calMains{ 0.2574 };       // Constant holding the calibration factor for the mains voltage measurement

const byte ssrPin{ 7 };      // SSR drive pin (output)
const byte zerocross{ 12 };  // ISR zerocross trigger pin (input)
const byte syncPin{ 5 };     // defines pin for the sync output to the TRMS power meter
const byte sensePin{ A2 };   // Analog Pin A2 reads voltage from mains plug
const byte stabOnPin{ 6 };   // input for an external on/off switch for turning stabilization on or off.

float mainsVoltage_actual{};          // Variable holding the mains voltage
float mainsAccumulator{};             // used for calculating the moving average mains voltage
int unsigned numberOfSamples{ 150 };  // used for calculating the moving average mains voltage
int unsigned sampleCounter{};         // used for calculating the moving average mains voltage
float powerMax_actual{};              // the potential power resulting from actual mains voltage and 100 percent setting
float powerMax_low{};                 // the potential power resulting from the lowest expected mains voltage and 100 percent setting
float powerStabilized{};              // the actual stabilized power resulting from the selected percent setting and the lowest expected mains voltage
float powerSelected_actual{};         // the power resulting from actual mains voltage and selected percent setting
float powerSelected_low{};            // the power resulting from lowest expected mains voltage and selected percent setting
float ratio_powerMax{};               // ratio for adjusting the selected Percent for stabilizing
float percentStabilized{};            // the selected percent, adjusted for stabilizing
bool stabilizationOn{ false };        // holding the status of the stab. on/off switch. True when stabilization is on.

bool setoutput{ false };              // SSR turns on when true, used in writePattern
volatile bool outputActive{ false };  // For use in the ISR and writePattern routines
                                      // ISR sets to true after zerocrossing falling edge,
                                      //writePattern sets to false after patternCounter is full.

int Maximum{ 100 };    //Maximum power IE 100% or 1000%
int Per_maximum{ 1 };  //selected percent setting Power % of the maximum value

int Pttn_1st{};  //1st Cycle Pattern }
int Pttn_2nd{};  //2nd Cycle Pattern }
int Pttn_3rd{};  //3rd Cycle Pattern } These are calculated in the createPattern() routine and
int Pttn_4th{};  //4th Cycle Pattern } used to turn cycles on or off in the writePattern() routine
int Pttn_5th{};  //5th Cycle Pattern }

int Reps_1st{};
int Skip_diff{};
int skip_diff_3{};
int skip_diff_4{};
int Skip{};
int Keep_skip{};
int Loose_keep_skip{};
int Restore_Loose_keep_skip{};

int Frame_counter{ 0 };  //Counter used in writePattern() routine. Each Mains cycle triggers ISR
int PttnCnt_1st{ 0 };    //PatternCounters used in writePattern()
int PttnCnt_2nd{ 0 };    //
int PttnCnt_3rd{ 0 };    //
int PttnCnt_4th{ 0 };    //
int PttnCnt_5th{ 0 };    //

// function prototype declarations
void create_pattern();  // function to calculate the pulse pattern, based of the required percentage value.
void writePattern();    // function for writing the pulse pattern to the SSR after each mains voltage zerocrossing

//##################################################################################################################

void setup() {
  Serial.begin(115200);  // activate Serial Monitor
  u8x8.begin();          // activate the OLED on SPI
  u8x8.setPowerSave(0);  // not sure if this is needed

  pinMode(syncPin, OUTPUT);          // output for optional sync signal to TRMS power meter
  pinMode(ssrPin, OUTPUT);           // Setup SSR output pin
  pinMode(zerocross, INPUT_PULLUP);  // Setup Zerocross input pin
  digitalWrite(syncPin, true);       // set sync signal to true till framecounter starts
  pinMode(stabOnPin, INPUT_PULLUP);  // Setup stabilization on/off input pin


  // Set Pin Change Interrupt Control Register and Pin Change Mask
  // to allow interrupt driven response to the zero crossing detector
  // connected to pin D12. (|= is the "compound bitwise or" operator)
  PCICR |= B00000001;   // Select port B in the Pin Change Interrupt Control Register
  PCMSK0 |= B00010000;  // Enable D12 pin in the Pin Change Mask 0

  powerMax_low = sq(mainsVoltage_low) / elementResistance;  // the  power resulting from the lowest expected
                                                            //  mains voltage and 100 percent setting
}


//##################################################################################################################

void loop() {
  writePattern();               // calls the routine to write the pulse pattern to the SSR
                                // sets and resets outputActive based on zerocrossing and frameCounter
  if (outputActive == false) {  // calls the routine to calculate the pulse pattern
                                // only after writePattern() has reset outputActive
    createPattern();
  }

  if (outputActive == false && timeElapsed1 > interval1) {   // read analog inputs only when not writing the pattern and
                                                             // after a certain interval has passed
    Per_maximum = map(analogRead(A0), 0, 1023, 1, Maximum);  // read and store potentiometer value as the selected Percent setting

    if (sampleCounter <= (numberOfSamples - 1)) {  // read actual mains voltage and calculate moving average
      mainsAccumulator = mainsAccumulator + analogRead(sensePin);
      sampleCounter++;
    } else {
      mainsVoltage_actual = (mainsAccumulator / numberOfSamples) * calMains;
      mainsAccumulator = 0;
      sampleCounter = 0;
    }

    powerMax_actual = sq(mainsVoltage_actual) / elementResistance;  // the potential power resulting from actual mains voltage and 100 percent setting
    powerSelected_actual = powerMax_actual * Per_maximum / 100;     // the power resulting from actual mains voltage and selected percent setting
    powerSelected_low = powerMax_low * Per_maximum / 100;           // the power resulting from lowest expected mains voltage and selected percent setting
    ratio_powerMax = powerMax_low / powerMax_actual;                // ratio for adjusting the selected Percent for stabilizing
    if (ratio_powerMax > 1) {                                       // do not increase selected percent when actual mains is
                                                                    // lower than the lower limit
      ratio_powerMax = 1;
    }
    percentStabilized = Per_maximum * ratio_powerMax;             // the selected percent, corrected for stabilizing
    powerStabilized = powerMax_actual * percentStabilized / 100;  // the actual stabilized power resulting from the selected percent setting
                                                                  // and the lowest expected mains voltage
    timeElapsed1 = 0;
  }


  if (outputActive == false && timeElapsed2 > interval2) {  // print data only when not writing the pattern and
                                                            // after a certain interval has passed
    stabilizationOn = digitalRead(stabOnPin);               // read status of stabilization on/off switch and
                                                            // change the display layout accordingly

    u8x8.setFont(u8x8_font_px437wyse700a_2x2_r);  // set large font
    u8x8.setCursor(0, 0);
    u8x8.print(Per_maximum);
    u8x8.print("%   ");

    u8x8.setFont(u8x8_font_chroma48medium8_r);  // set small font for "stab" and "on" or "off"
    u8x8.setCursor(9, 0);
    u8x8.print("Stab");
    u8x8.setCursor(9, 1);
    if (stabilizationOn == true) {
      u8x8.print("On");
    } else {
      u8x8.print("Off");
    }

    u8x8.setFont(u8x8_font_px437wyse700a_2x2_r);  // set large font for the power display
    u8x8.setCursor(0, 2);
    if (stabilizationOn == true) {
      u8x8.print(roundf(powerStabilized));
      u8x8.print(" W   ");
    } else {
      u8x8.print(roundf(powerSelected_actual));
      u8x8.print(" W    ");
    }
    if (outputActive == true) return;

    u8x8.setFont(u8x8_font_chroma48medium8_r);  // set small font for the voltage readouts
    u8x8.setCursor(0, 5);
    u8x8.print(roundf(mainsVoltage_actual));
    u8x8.print(" V actual");
    if (outputActive == true) return;
    u8x8.setCursor(0, 6);
    u8x8.print(mainsVoltage_low, 0);
    u8x8.print(" V stab");


    Serial.print(Per_maximum);
    Serial.print(" Percent");
    Serial.print("   Mains:");
    Serial.print(mainsVoltage_actual);
    Serial.print("   powerMax_actual:");
    Serial.print(powerMax_actual);
    Serial.print("   powerStabilized:");
    Serial.println(powerStabilized);

    timeElapsed2 = 0;
  }

}  // loop end
//##############################################################################

//###########Function for writing the calculated pattern to the SSR ############
//###########and setting and resetting the outputActive flag ###################
void writePattern() {
  if (outputActive == true) {  // set by the ISR after each falling zerocrossing

    digitalWrite(ssrPin, setoutput);  //Turn SSR on or off as determined by previous pattern calculation
    outputActive = false;             // reset the outputActive flag to prevent from blocking the other activities in the loop

    //Calculate state of "setoutput" for use after the next falling zerocrossing.
    //This state will be based on the Patterns calculated in Main loop, using the desired percentage of power.
    if (Frame_counter == Maximum + 1 - 5) { digitalWrite(syncPin, 1); }  // set sync signal to HIGH after 95 frames
                                                                         // (100 millisecs before next zerocrossing)
    if (Frame_counter >= (Maximum + 1)) {
      digitalWrite(syncPin, 0);  // set sync signal to LOW when frame counter starts
                                 // in order to allow optional start of the TRMS power meter
      Frame_counter = 1;
      PttnCnt_1st = 0;
      PttnCnt_2nd = 0;
      PttnCnt_3rd = 0;
      PttnCnt_4th = 0;
      PttnCnt_5th = 0;
      setoutput = false;
    }

    Frame_counter++;
    PttnCnt_1st++;  // inc Pttn_1st counter

    if (PttnCnt_1st < (Pttn_1st))  // ie. Pttn_1st is 2 when 37% power is selected
    {
      setoutput = false;  //Turn off   SSR if pttn_1st hasn't arrived
      return;             // and return to loop
    }

    setoutput = true;  // Turn on SSR as PttnCnt_1st has reached  pttn_1st (ie. 2 for 37%)
    PttnCnt_1st = 0;   // Reset Pttn_1st counter
    PttnCnt_2nd++;     // inc Skip counter

    if (Pttn_2nd == 0)  // if no pulses need to be skipped
    {
      setoutput = true;  // leave it turned on
      return;            // and return to loop
    }

    if (PttnCnt_2nd < (Pttn_2nd))  // ie. Pttn_2nd is 3 when 37% power is selected
    {
      setoutput = true;  // keep SSR on if not Skip rate hasn't arrived
      return;
    }

    setoutput = false;  //Turn off SSR as Skip rate has arrived
    PttnCnt_2nd = 0;

    PttnCnt_3rd++;  //inc Keepskip counter
    if (Pttn_3rd == 0) {
      setoutput = false;
      return;
    }
    if (PttnCnt_3rd < (Pttn_3rd)) {
      setoutput = false;
      return;
    }
    setoutput = true;
    PttnCnt_3rd = 0;

    PttnCnt_4th++;
    if (Pttn_4th == 0) {
      setoutput = true;
      return;
    }
    if (PttnCnt_4th < (Pttn_4th)) {
      setoutput = true;  // keep SSR on if not Skip rate hasn't arrived
      return;
    }
    setoutput = false;  //Turn off SSR as Skip rate has arrived
    PttnCnt_4th = 0;
    PttnCnt_5th++;
    if (Pttn_5th == 0) {
      setoutput = true;
      return;
    }
    if (PttnCnt_5th < (Pttn_5th)) {
      setoutput = false;
      return;
    }  // keep SSR on if not Skip rate hasn't arrived

    setoutput = true;  //Turn on SSR as Skip rate has arrived
    PttnCnt_5th = 0;
  }
}


//#############calculate the pulse pattern#########################
void createPattern() {
  if (Per_maximum == 0) Pttn_1st = 0;     //Pttn_1st is first fill
  else Pttn_1st = Maximum / Per_maximum;  //ie turn on every 2nd cycle if 37% is desired  100/37=2

  if (Pttn_1st == 0) Reps_1st = 0;
  else Reps_1st = (Maximum / Pttn_1st);  // i.e 100/2=50 pulsed per cycle

  Skip = Reps_1st - Per_maximum;  // Pttn_2nd runs through first fill and removes certain cycles IE, every 5th
  // i.e for 37% 50-37 = 13
  if (Skip == 0) Pttn_2nd = 0;
  else Pttn_2nd = (Reps_1st / Skip);  // i.e for 37% 50/13=3

  if (Pttn_2nd == 0) Skip_diff = 0;
  else Skip_diff = (Reps_1st / Pttn_2nd);  // i.e for 37% 50/3=16 pulses out of 50 shall be skipped

  Keep_skip = Skip_diff - Skip;  // Pttn_3rd turns back on cycles switched off by Skip-rate
                                 // to correct missing cycles i.e for 37% 16-13=3
  if (Keep_skip == 0) Pttn_3rd = 0;
  else Pttn_3rd = (Skip_diff / Keep_skip);  // i.e for 37% 16/3=5

  if (Pttn_3rd == 0) skip_diff_3 = 0;
  else skip_diff_3 = Skip_diff / Pttn_3rd;  // i.e for 37% 16/5=3

  Loose_keep_skip = skip_diff_3 - Keep_skip;  //Pttn_4th turns off required cycles after Pttn_3rd run
                                              // i.e for 37% 3-3=0
  if (Loose_keep_skip == 0) Pttn_4th = 0;
  else Pttn_4th = skip_diff_3 / Loose_keep_skip;  // i.e for 37% 3/3=1

  if (Pttn_4th == 0) skip_diff_4 = 0;
  else skip_diff_4 = skip_diff_3 / Pttn_4th;

  Restore_Loose_keep_skip = skip_diff_4 - Loose_keep_skip;  //Pttn_5th turns on required cycles after Pttn_4rd run
  if (Restore_Loose_keep_skip == 0) Pttn_5th = 0;
  else Pttn_5th = skip_diff_4 / Restore_Loose_keep_skip;
}

//########Interrupt Service Routine for the zerocrossing detector ##############
ISR(PCINT0_vect)  // ISR for pin change interrupt on port B pin 12 (Zerocrossing detected )
{
  if (digitalRead(zerocross) == false) {  // react only when pinchange was falling edge
                                          // otherwise do nothing and return
    outputActive = true;                  // enable the routine for writing the output pattern
                                          // to the SSR with any falling edge
  }
}

This is the complete wiring schematic:
Burstfire_Controller.pdf
(66.88 KiB) Downloaded 43 times
Please feel free to suggest corrections or ask questions.
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Here is Rev. 1 of the schematic. I had omitted a diode at the optocoupler of the zero crossing detector.
burstfire_controller_rev1.pdf
(67.84 KiB) Downloaded 57 times
User avatar
Black Bull
Novice
Posts: 88
Joined: Fri Aug 16, 2024 11:54 pm
Location: The Brew Room

Re: Yummy's DIY Burst fire controller project

Post by Black Bull »

Yummy, read this as I want to upgrade my controller....
Now my head just hurts and I've gone cross eyed :wtf:
I'm not fat, I'm in shape....Round is a shape
Getting old has whiskers on it !
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Other error corrections are needed in my project.
I had swapped pins 4 and 5 at optocoupler U1 and used an incorrect symbol.
Here is Rev.2 of the schematic:
Burstfire_Controller_Rev2.pdf
(68.42 KiB) Downloaded 39 times
There was a glitch in the code for printing to the OLED. It caused the Arduino to freeze when 100% was selected with the potentiometer while the Stabilization was set to on.

The erroneous code is

Code: Select all

    if (stabilizationOn == true) {
      u8x8.print(roundf(powerStabilized));
      u8x8.print(" W   ");
    } else {
      u8x8.print(roundf(powerSelected_actual));

      u8x8.print(" W    ");
    }

The corrected code is

Code: Select all

    if (stabilizationOn == true) {
      u8x8.print(roundf(powerStabilized));
      u8x8.print(" W   ");
    } else {
      u8x8.print(roundf(powerSelected_actual));

      u8x8.print(" W   ");
    }

The difference is not obvious. The line

Code: Select all

      u8x8.print(" W    ");
includes 4 spaces after the W.
The corrected code used only 3 spaces.

Here is the corrected .ino file (remove the fake .pdf ending of the filename ofter download.
Burstfire_5_SPI_Stab_5.ino.pdf
(17.38 KiB) Downloaded 41 times
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

I found another error in my code.
In line 161, the following line must be inserted:

Code: Select all

if(stabilizationOn == true) Per_maximum = percentStabilized;                                     
Otherwise only the display showed the reduced, stabilized power, however the heating element was still supplied with the unstabilized power.




Sorry!!

Here is the code:
Burstfire_5_SPI_Stab_6.ino.pdf
(17.5 KiB) Downloaded 42 times
Remove the .pdf file ending after download.

Code: Select all

/*########################################################################################

The sketch is based on the one created by Yummyrum and posted at HD 
June 25th, 2025 https://homedistiller.org/forum/viewtopic.php?p=7787168#p7787168
and is modified by kennstminet August 2024 filename Burstfire_5_Stab_6.ino

- Removed LCD code, added code for use of an 1.3 inch OLED display (SH1106) with SPI interface.
  I had tested with a similar OLED with I2C interface and found that I2C is too slow and it was disturbing the burstfire timing.
- Introduced a transformerless voltage sensor for detecting the mains voltage zerocrossing
- Added an optional synchronization output to allow syncing the timing with Yummyrums TRMS power meter.
  I found that such sycronization is not giving appreciable benefits. I left the code active, in case somebody needs it.
  The sync output is set to LOW at start of the Frame_counter and returns to HIGH 100 millisecs before the Frame_counter is full
- Removed the code for writing the pattern to the SSR from the ISR and made a regular function for it.
  Now, the ISR just sets a variable at zerocrossing and then returns to loop to avoid blocking the microcontroller for full 2 seconds.
- Added the "volatile" attribute to the variable used in the ISR
- Added code for measuring the current mains voltage (from the wall outlet). An external voltage sensor is needed for translating the 
  mains AC voltage to a 0 to 5VDC signal to the respective analog input of the arduino. 
- Added code to provide a means of power stabilization. It compares the actual mains voltage with a preset 
  "lowest expected mains voltage" and reduces the selected power accordingly if the actual mains voltage is higher than the lower limit.
  The max. available stabilized power is therefor lower than the power in unstabilized mode.
  However, it will remain stable, when the mains voltage is variing as long it is higher than the lowr limit.
- Added an digital input for a external switch to turn on and off the stabilization function.






##########################################################################################*/

#include <Arduino.h>        // I have no idea why this library is used here, so left it
#include <U8x8lib.h>        // library for the OLED
#include <SPI.h>            // for the OLED SPI interface
#include <math.h>           // for mathematical rounding function "roundf()2
#include "elapsedMillis.h"  // for creating a timer for the measurement duration

U8X8_SH1106_128X64_NONAME_4W_HW_SPI u8x8(/* cs=*/10, /* dc=*/9, /* reset=*/8);  //creates an OLED object "u8x8"
elapsedMillis timeElapsed1;                                                     // timer object for reading the analog inputs
elapsedMillis timeElapsed2;                                                     // timer object for printing data

unsigned int interval1{ 10 };  // Timer interval for reading the analog inputs
unsigned int interval2{ 10 };  // Timer interval for printing data

const float mainsVoltage_low{ 210 };  // the lowest voltage, the stabilizer should be able to compensate
const float elementResistance{ 19.70 };  // Constant holding the resistance of the heating element in Ohms.
const float calMains{ 0.2574 };       // Constant holding the calibration factor for the mains voltage measurement

const byte ssrPin{ 7 };      // SSR drive pin (output)
const byte zerocross{ 12 };  // ISR zerocross trigger pin (input)
const byte potPin{ A0 };     // Analog Pin A0 reads the potentionmeter for the percdent setting
const byte syncPin{ 5 };     // defines pin for the sync output to the TRMS power meter
const byte sensePin{ A2 };   // Analog Pin A2 reads voltage from mains plug
const byte stabOnPin{ 6 };   // input for an external on/off switch for turning stabilization on or off.

float mainsVoltage_actual{};          // Variable holding the mains voltage
float mainsAccumulator{};             // used for calculating the moving average mains voltage
int unsigned numberOfSamples{ 150 };  // used for calculating the moving average mains voltage
int unsigned sampleCounter{};         // used for calculating the moving average mains voltage
float powerMax_actual{};              // the potential power resulting from actual mains voltage and 100 percent setting
float powerMax_low{};                 // the potential power resulting from the lowest expected mains voltage and 100 percent setting
float powerStabilized{};              // the actual stabilized power resulting from the selected percent setting and the lowest expected mains voltage
float powerSelected_actual{};         // the power resulting from actual mains voltage and selected percent setting
float powerSelected_low{};            // the power resulting from lowest expected mains voltage and selected percent setting
float ratio_powerMax{};               // ratio for adjusting the selected Percent for stabilizing
float percentStabilized{};            // the selected percent, adjusted for stabilizing
bool stabilizationOn{ false };        // holding the status of the stab. on/off switch. True when stabilization is on.

bool setoutput{ false };              // SSR turns on when true, used in writePattern
volatile bool outputActive{ false };  // For use in the ISR and writePattern routines
                                      // ISR sets to true after zerocrossing falling edge,
                                      //writePattern sets to false after patternCounter is full.

int Maximum{ 100 };    //Maximum power IE 100% or 1000%
int Per_maximum{ 1 };  //selected percent setting Power % of the maximum value

int Pttn_1st{};  //1st Cycle Pattern }
int Pttn_2nd{};  //2nd Cycle Pattern }
int Pttn_3rd{};  //3rd Cycle Pattern } These are calculated in the createPattern() routine and
int Pttn_4th{};  //4th Cycle Pattern } used to turn cycles on or off in the writePattern() routine
int Pttn_5th{};  //5th Cycle Pattern }

int Reps_1st{};
int Skip_diff{};
int skip_diff_3{};
int skip_diff_4{};
int Skip{};
int Keep_skip{};
int Loose_keep_skip{};
int Restore_Loose_keep_skip{};

int Frame_counter{ 0 };  //Counter used in writePattern() routine. Each Mains cycle triggers ISR
int PttnCnt_1st{ 0 };    //PatternCounters used in writePattern()
int PttnCnt_2nd{ 0 };    //
int PttnCnt_3rd{ 0 };    //
int PttnCnt_4th{ 0 };    //
int PttnCnt_5th{ 0 };    //

// function prototype declarations
void create_pattern();  // function to calculate the pulse pattern, based of the required percentage value.
void writePattern();    // function for writing the pulse pattern to the SSR after each mains voltage zerocrossing

//##################################################################################################################

void setup() {
  Serial.begin(115200);  // activate Serial Monitor
  u8x8.begin();          // activate the OLED on SPI
  u8x8.setPowerSave(0);  // not sure if this is needed

  pinMode(syncPin, OUTPUT);          // output for optional sync signal to TRMS power meter
  pinMode(ssrPin, OUTPUT);           // Setup SSR output pin
  pinMode(zerocross, INPUT_PULLUP);  // Setup Zerocross input pin
  digitalWrite(syncPin, true);       // set sync signal to true till framecounter starts
  pinMode(stabOnPin, INPUT_PULLUP);  // Setup stabilization on/off input pin


  // Set Pin Change Interrupt Control Register and Pin Change Mask
  // to allow interrupt driven response to the zero crossing detector
  // connected to pin D12. (|= is the "compound bitwise or" operator)
  PCICR |= B00000001;   // Select port B in the Pin Change Interrupt Control Register
  PCMSK0 |= B00010000;  // Enable D12 pin in the Pin Change Mask 0

  powerMax_low = sq(mainsVoltage_low) / elementResistance;  // the  power resulting from the lowest expected
                                                            //  mains voltage and 100 percent setting
}


//##################################################################################################################

void loop() {
  writePattern();               // calls the routine to write the pulse pattern to the SSR
                                // sets and resets outputActive based on zerocrossing and frameCounter
  if (outputActive == false) {  // calls the routine to calculate the pulse pattern
                                // only after writePattern() has reset outputActive
    createPattern();
  }

  if (outputActive == false && timeElapsed1 > interval1) {       // read analog inputs only when not writing the pattern and
                                                                 // after a certain interval has passed
    Per_maximum = map(analogRead(potPin), 0, 1023, 1, Maximum);  // read and store potentiometer value as the selected Percent setting

    if (sampleCounter <= (numberOfSamples - 1)) {  // read actual mains voltage and calculate moving average
      mainsAccumulator = mainsAccumulator + analogRead(sensePin);
      sampleCounter++;
    } else {
      mainsVoltage_actual = (mainsAccumulator / numberOfSamples) * calMains;
      mainsAccumulator = 0;
      sampleCounter = 0;
    }

    powerMax_actual = sq(mainsVoltage_actual) / elementResistance;  // the potential power resulting from actual mains voltage and 100 percent setting
    powerSelected_actual = powerMax_actual * Per_maximum / 100;     // the power resulting from actual mains voltage and selected percent setting
    powerSelected_low = powerMax_low * Per_maximum / 100;           // the power resulting from lowest expected mains voltage and selected percent setting
    ratio_powerMax = powerMax_low / powerMax_actual;                // ratio for adjusting the selected Percent for stabilizing
    if (ratio_powerMax > 1) {                                       // do not increase selected percent when actual mains is
                                                                    // lower than the lower limit
      ratio_powerMax = 1;
    }
    percentStabilized = Per_maximum * ratio_powerMax;             // the selected percent, corrected for stabilizing
    powerStabilized = powerMax_actual * percentStabilized / 100;  // the actual stabilized power resulting from the selected percent setting
                                                                  // and the lowest expected mains voltage
    if(stabilizationOn == true) Per_maximum = percentStabilized;                                                          
    timeElapsed1 = 0;
  }


  if (outputActive == false && timeElapsed2 > interval2) {  // print data only when not writing the pattern and
                                                            // after a certain interval has passed
    stabilizationOn = digitalRead(stabOnPin);               // read status of stabilization on/off switch and
                                                            // change the display layout accordingly

    u8x8.setFont(u8x8_font_px437wyse700a_2x2_r);  // set large font
    u8x8.setCursor(0, 0);
    u8x8.print(Per_maximum);
    u8x8.print("%   ");

    u8x8.setFont(u8x8_font_chroma48medium8_r);  // set small font for "stab" and "on" or "off"
    u8x8.setCursor(9, 0);
    u8x8.print("Stab");
    u8x8.setCursor(9, 1);
    if (stabilizationOn == true) {
      u8x8.print("On");
    } else {
      u8x8.print("Off");
    }

    u8x8.setFont(u8x8_font_px437wyse700a_2x2_r);  // set large font for the power display
    u8x8.setCursor(0, 2);
    if (stabilizationOn == true) {
      u8x8.print(roundf(powerStabilized));
      u8x8.print(" W   ");
    } else {
      u8x8.print(roundf(powerSelected_actual));

      u8x8.print(" W   ");
    }
    if (outputActive == true) return;

    u8x8.setFont(u8x8_font_chroma48medium8_r);  // set small font for the voltage readouts
    u8x8.setCursor(0, 5);
    u8x8.print(roundf(mainsVoltage_actual));
    u8x8.print(" V actual");
    if (outputActive == true) return;
    u8x8.setCursor(0, 6);
    u8x8.print(mainsVoltage_low, 0);
    u8x8.print(" V stab");


    Serial.print(percentStabilized);
    Serial.print(" Percent stab.   ");
    Serial.print(  Per_maximum);
    Serial.print(" Percent");

    Serial.print("   Mains:");
    Serial.print(mainsVoltage_actual);
    Serial.print("   powerMax_actual:");
    Serial.print(powerMax_actual);
    Serial.print("   powerStabilized:");
    Serial.print(powerStabilized);
    Serial.print(  " pSa");
    Serial.print(powerSelected_actual);
    Serial.print("  Stab: ");
    Serial.println(stabilizationOn);

    timeElapsed2 = 0;
  }

}  // loop end
//##############################################################################

//###########Function for writing the calculated pattern to the SSR ############
//###########and setting and resetting the outputActive flag ###################
void writePattern() {
  if (outputActive == true) {  // set by the ISR after each falling zerocrossing

    digitalWrite(ssrPin, setoutput);  //Turn SSR on or off as determined by previous pattern calculation
    outputActive = false;             // reset the outputActive flag to prevent from blocking the other activities in the loop

    //Calculate state of "setoutput" for use after the next falling zerocrossing.
    //This state will be based on the Patterns calculated in Main loop, using the desired percentage of power.
    if (Frame_counter == Maximum + 1 - 5) { digitalWrite(syncPin, 1); }  // set sync signal to HIGH after 95 frames
                                                                         // (100 millisecs before next zerocrossing)
    if (Frame_counter >= (Maximum + 1)) {
      digitalWrite(syncPin, 0);  // set sync signal to LOW when frame counter starts
                                 // in order to allow optional start of the TRMS power meter
      Frame_counter = 1;
      PttnCnt_1st = 0;
      PttnCnt_2nd = 0;
      PttnCnt_3rd = 0;
      PttnCnt_4th = 0;
      PttnCnt_5th = 0;
      setoutput = false;
    }

    Frame_counter++;
    PttnCnt_1st++;  // inc Pttn_1st counter

    if (PttnCnt_1st < (Pttn_1st))  // ie. Pttn_1st is 2 when 37% power is selected
    {
      setoutput = false;  //Turn off   SSR if pttn_1st hasn't arrived
      return;             // and return to loop
    }

    setoutput = true;  // Turn on SSR as PttnCnt_1st has reached  pttn_1st (ie. 2 for 37%)
    PttnCnt_1st = 0;   // Reset Pttn_1st counter
    PttnCnt_2nd++;     // inc Skip counter

    if (Pttn_2nd == 0)  // if no pulses need to be skipped
    {
      setoutput = true;  // leave it turned on
      return;            // and return to loop
    }

    if (PttnCnt_2nd < (Pttn_2nd))  // ie. Pttn_2nd is 3 when 37% power is selected
    {
      setoutput = true;  // keep SSR on if not Skip rate hasn't arrived
      return;
    }

    setoutput = false;  //Turn off SSR as Skip rate has arrived
    PttnCnt_2nd = 0;

    PttnCnt_3rd++;  //inc Keepskip counter
    if (Pttn_3rd == 0) {
      setoutput = false;
      return;
    }
    if (PttnCnt_3rd < (Pttn_3rd)) {
      setoutput = false;
      return;
    }
    setoutput = true;
    PttnCnt_3rd = 0;

    PttnCnt_4th++;
    if (Pttn_4th == 0) {
      setoutput = true;
      return;
    }
    if (PttnCnt_4th < (Pttn_4th)) {
      setoutput = true;  // keep SSR on if not Skip rate hasn't arrived
      return;
    }
    setoutput = false;  //Turn off SSR as Skip rate has arrived
    PttnCnt_4th = 0;
    PttnCnt_5th++;
    if (Pttn_5th == 0) {
      setoutput = true;
      return;
    }
    if (PttnCnt_5th < (Pttn_5th)) {
      setoutput = false;
      return;
    }  // keep SSR on if not Skip rate hasn't arrived

    setoutput = true;  //Turn on SSR as Skip rate has arrived
    PttnCnt_5th = 0;
  }
}


//#############calculate the pulse pattern#########################
void createPattern() {
  if (Per_maximum == 0) Pttn_1st = 0;     //Pttn_1st is first fill
  else Pttn_1st = Maximum / Per_maximum;  //ie turn on every 2nd cycle if 37% is desired  100/37=2

  if (Pttn_1st == 0) Reps_1st = 0;
  else Reps_1st = (Maximum / Pttn_1st);  // i.e 100/2=50 pulsed per cycle

  Skip = Reps_1st - Per_maximum;  // Pttn_2nd runs through first fill and removes certain cycles IE, every 5th
  // i.e for 37% 50-37 = 13
  if (Skip == 0) Pttn_2nd = 0;
  else Pttn_2nd = (Reps_1st / Skip);  // i.e for 37% 50/13=3

  if (Pttn_2nd == 0) Skip_diff = 0;
  else Skip_diff = (Reps_1st / Pttn_2nd);  // i.e for 37% 50/3=16 pulses out of 50 shall be skipped

  Keep_skip = Skip_diff - Skip;  // Pttn_3rd turns back on cycles switched off by Skip-rate
                                 // to correct missing cycles i.e for 37% 16-13=3
  if (Keep_skip == 0) Pttn_3rd = 0;
  else Pttn_3rd = (Skip_diff / Keep_skip);  // i.e for 37% 16/3=5

  if (Pttn_3rd == 0) skip_diff_3 = 0;
  else skip_diff_3 = Skip_diff / Pttn_3rd;  // i.e for 37% 16/5=3

  Loose_keep_skip = skip_diff_3 - Keep_skip;  //Pttn_4th turns off required cycles after Pttn_3rd run
                                              // i.e for 37% 3-3=0
  if (Loose_keep_skip == 0) Pttn_4th = 0;
  else Pttn_4th = skip_diff_3 / Loose_keep_skip;  // i.e for 37% 3/3=1

  if (Pttn_4th == 0) skip_diff_4 = 0;
  else skip_diff_4 = skip_diff_3 / Pttn_4th;

  Restore_Loose_keep_skip = skip_diff_4 - Loose_keep_skip;  //Pttn_5th turns on required cycles after Pttn_4rd run
  if (Restore_Loose_keep_skip == 0) Pttn_5th = 0;
  else Pttn_5th = skip_diff_4 / Restore_Loose_keep_skip;
}

//########Interrupt Service Routine for the zerocrossing detector ##############
ISR(PCINT0_vect)  // ISR for pin change interrupt on port B pin 12 (Zerocrossing detected )
{
  if (digitalRead(zerocross) == false) {  // react only when pinchange was falling edge
                                          // otherwise do nothing and return
    outputActive = true;                  // enable the routine for writing the output pattern
                                          // to the SSR with any falling edge
  }
}

kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

The error correction in the previous post introduced a new error.
The line 160

Code: Select all

if(stabilizationOn == true) Per_maximum = percentStabilized; 
assignes a float (percentStabilized) to an int type variable (Per_maximum), cutting off the decimals.
I changed to code to

Code: Select all

if(stabilizationOn == true) Per_maximum = int(roundf(percentStabilized));
In addition, there was an old glitch in the code, causing the SSR to turn on full power when the selected Power was set to 0%.
I had mitigated the problem by mapping the range of the potentiometer to 1 to Maximum, avoiding the setting 0.
However since adding the power stabilization feature, the value of Per_maximum could drop below 1 when the mains voltage was high.
The dormant problem of causing the SSR to turn on full power when the selected Power was set to 0% showed up again.
As a solution, I added the line

Code: Select all

    if(Per_maximum == 0) return;      // do not write to SSR when Per_maximum is set to 0
to the function writePattern() in line 235 to cause an early return to loop().
The function now looks like:

Code: Select all

//###########Function for writing the calculated pattern to the SSR ############
//###########and setting and resetting the outputActive flag ###################
void writePattern() {
  if (outputActive == true) {  // set by the ISR after each falling zerocrossing
    digitalWrite(ssrPin, setoutput);  //Turn SSR on or off as determined by previous pattern calculation
    outputActive = false;             // reset the outputActive flag to prevent from blocking the other activities in the loop
    if(Per_maximum == 0) return;      // do not write to SSR when Per_maximum is set to 0
	.......
	.......
This allowed me to remove the previous restriction, avoiding the 0% setting. Now it is possible to turn off the power by selecting 0%.
Line 73 is now

Code: Select all

int Per_maximum{ 0 };
and line 138 is now

Code: Select all

Per_maximum = map(analogRead(potPin), 0, 1023, 0, Maximum);
Here is the full sketch with filename Burstfire_5_Stab_7.ino

Code: Select all

/*########################################################################################

The sketch is based on the one created by Yummyrum and posted at HD 
June 25th, 2025 https://homedistiller.org/forum/viewtopic.php?p=7787168#p7787168
and is modified by kennstminet October 2024 filename Burstfire_5_Stab_7.ino

- Removed LCD code, added code for use of an 1.3 inch OLED display (SH1106) with SPI interface.
  I had tested with a similar OLED with I2C interface and found that I2C is too slow and it was disturbing the burstfire timing.
- Introduced a transformerless voltage sensor for detecting the mains voltage zerocrossing
- Added an optional synchronization output to allow syncing the timing with Yummyrums TRMS power meter.
  I found that such sycronization is not giving appreciable benefits. I left the code active, in case somebody needs it.
  The sync output is set to LOW at start of the Frame_counter and returns to HIGH 100 millisecs before the Frame_counter is full
- Removed the code for writing the pattern to the SSR from the ISR and made a regular function for it.
  Now, the ISR just sets a variable at zerocrossing and then returns to loop to avoid blocking the microcontroller for full 2 seconds.
- Added the "volatile" attribute to the variable used in the ISR
- Added code for measuring the current mains voltage (from the wall outlet). An external voltage sensor is needed for translating the 
  mains AC voltage to a 0 to 5VDC signal to the respective analog input of the arduino. 
- Added code to provide a means of power stabilization. It compares the actual mains voltage with a preset 
  "lowest expected mains voltage" and reduces the selected power accordingly if the actual mains voltage is higher than the lower limit.
  The max. available stabilized power is therefor lower than the power in unstabilized mode.
  However, it will remain stable, when the mains voltage is variing as long it is higher than the lowr limit.
- Added an digital input for a external switch to turn on and off the stabilization function.
- prevent SSR from turning on full power when Per_maximum is set to 0 (line 235)
  Initialize Per_maximum to 0 (line 73) and map potentiometer to the range 0 to Maximum (LINE 138)
- correct error line 160, cast float to int before assigning it


##########################################################################################*/

#include <Arduino.h>        // I have no idea why this library is used here, so left it
#include <U8x8lib.h>        // library for the OLED
#include <SPI.h>            // for the OLED SPI interface
#include <math.h>           // for mathematical rounding function "roundf()2
#include "elapsedMillis.h"  // for creating a timer for the measurement duration

U8X8_SH1106_128X64_NONAME_4W_HW_SPI u8x8(/* cs=*/10, /* dc=*/9, /* reset=*/8);  //creates an OLED object "u8x8"
elapsedMillis timeElapsed1;                                                     // timer object for reading the analog inputs
elapsedMillis timeElapsed2;                                                     // timer object for printing data

unsigned int interval1{ 10 };  // Timer interval for reading the analog inputs
unsigned int interval2{ 10 };  // Timer interval for printing data

const float mainsVoltage_low{ 210 };  // the lowest voltage, the stabilizer should be able to compensate
const float elementResistance{ 19.70 };  // Constant holding the resistance of the heating element in Ohms.
const float calMains{ 0.2574 };       // Constant holding the calibration factor for the mains voltage measurement

const byte ssrPin{ 7 };      // SSR drive pin (output)
const byte zerocross{ 12 };  // ISR zerocross trigger pin (input)
const byte potPin{ A0 };     // Analog Pin A0 reads the potentionmeter for the percdent setting
const byte syncPin{ 5 };     // defines pin for the sync output to the TRMS power meter
const byte sensePin{ A2 };   // Analog Pin A2 reads voltage from mains plug
const byte stabOnPin{ 6 };   // input for an external on/off switch for turning stabilization on or off.

float mainsVoltage_actual{};          // Variable holding the mains voltage
float mainsAccumulator{};             // used for calculating the moving average mains voltage
int unsigned numberOfSamples{ 150 };  // used for calculating the moving average mains voltage
int unsigned sampleCounter{};         // used for calculating the moving average mains voltage
float powerMax_actual{};              // the potential power resulting from actual mains voltage and 100 percent setting
float powerMax_low{};                 // the potential power resulting from the lowest expected mains voltage and 100 percent setting
float powerStabilized{};              // the actual stabilized power resulting from the selected percent setting and the lowest expected mains voltage
float powerSelected_actual{};         // the power resulting from actual mains voltage and selected percent setting
float powerSelected_low{};            // the power resulting from lowest expected mains voltage and selected percent setting
float ratio_powerMax{};               // ratio for adjusting the selected Percent for stabilizing
float percentStabilized{};            // the selected percent, adjusted for stabilizing
bool stabilizationOn{ false };        // holding the status of the stab. on/off switch. True when stabilization is on.

bool setoutput{ false };              // SSR turns on when true, used in writePattern
volatile bool outputActive{ false };  // For use in the ISR and writePattern routines
                                      // ISR sets to true after zerocrossing falling edge,
                                      //writePattern sets to false after patternCounter is full.

int Maximum{ 100 };    //Maximum power IE 100% or 1000%
int Per_maximum{ 0 };  //selected percent setting Power % of the maximum value

int Pttn_1st{};  //1st Cycle Pattern }
int Pttn_2nd{};  //2nd Cycle Pattern }
int Pttn_3rd{};  //3rd Cycle Pattern } These are calculated in the createPattern() routine and
int Pttn_4th{};  //4th Cycle Pattern } used to turn cycles on or off in the writePattern() routine
int Pttn_5th{};  //5th Cycle Pattern }

int Reps_1st{};
int Skip_diff{};
int skip_diff_3{};
int skip_diff_4{};
int Skip{};
int Keep_skip{};
int Loose_keep_skip{};
int Restore_Loose_keep_skip{};

int Frame_counter{ 0 };  //Counter used in writePattern() routine. Each Mains cycle triggers ISR
int PttnCnt_1st{ 0 };    //PatternCounters used in writePattern()
int PttnCnt_2nd{ 0 };    //
int PttnCnt_3rd{ 0 };    //
int PttnCnt_4th{ 0 };    //
int PttnCnt_5th{ 0 };    //

// function prototype declarations
void create_pattern();  // function to calculate the pulse pattern, based of the required percentage value.
void writePattern();    // function for writing the pulse pattern to the SSR after each mains voltage zerocrossing

//##################################################################################################################

void setup() {
  Serial.begin(115200);  // activate Serial Monitor
  u8x8.begin();          // activate the OLED on SPI
  u8x8.setPowerSave(0);  // not sure if this is needed

  pinMode(syncPin, OUTPUT);          // output for optional sync signal to TRMS power meter
  pinMode(ssrPin, OUTPUT);           // Setup SSR output pin
  pinMode(zerocross, INPUT_PULLUP);  // Setup Zerocross input pin
  digitalWrite(syncPin, true);       // set sync signal to true till framecounter starts
  pinMode(stabOnPin, INPUT_PULLUP);  // Setup stabilization on/off input pin


  // Set Pin Change Interrupt Control Register and Pin Change Mask
  // to allow interrupt driven response to the zero crossing detector
  // connected to pin D12. (|= is the "compound bitwise or" operator)
  PCICR |= B00000001;   // Select port B in the Pin Change Interrupt Control Register
  PCMSK0 |= B00010000;  // Enable D12 pin in the Pin Change Mask 0

  powerMax_low = sq(mainsVoltage_low) / elementResistance;  // the  power resulting from the lowest expected
                                                            //  mains voltage and 100 percent setting
}


//##################################################################################################################

void loop() {
  writePattern();               // calls the routine to write the pulse pattern to the SSR
                                // sets and resets outputActive based on zerocrossing and frameCounter
  if (outputActive == false) {  // calls the routine to calculate the pulse pattern
                                // only after writePattern() has reset outputActive
    createPattern();
  }

  if (outputActive == false && timeElapsed1 > interval1) {       // read analog inputs only when not writing the pattern and
                                                                 // after a certain interval has passed
    Per_maximum = map(analogRead(potPin), 0, 1023, 0, Maximum);  // read and store potentiometer value as the selected Percent setting

    if (sampleCounter <= (numberOfSamples - 1)) {  // read actual mains voltage and calculate moving average
      mainsAccumulator = mainsAccumulator + analogRead(sensePin);
      sampleCounter++;
    } else {
      mainsVoltage_actual = (mainsAccumulator / numberOfSamples) * calMains;
      mainsAccumulator = 0;
      sampleCounter = 0;
    }

    powerMax_actual = sq(mainsVoltage_actual) / elementResistance;  // the potential power resulting from actual mains voltage and 100 percent setting
    powerSelected_actual = powerMax_actual * Per_maximum / 100;     // the power resulting from actual mains voltage and selected percent setting
    powerSelected_low = powerMax_low * Per_maximum / 100;           // the power resulting from lowest expected mains voltage and selected percent setting
    ratio_powerMax = powerMax_low / powerMax_actual;                // ratio for adjusting the selected Percent for stabilizing
    if (ratio_powerMax > 1) {                                       // do not increase selected percent when actual mains is
                                                                    // lower than the lower limit
      ratio_powerMax = 1;
    }
    percentStabilized = Per_maximum * ratio_powerMax;             // the selected percent, corrected for stabilizing
    powerStabilized = powerMax_actual * percentStabilized / 100;  // the actual stabilized power resulting from the selected percent setting
                                                                  // and the lowest expected mains voltage
    if(stabilizationOn == true) Per_maximum = int(roundf(percentStabilized));                                                          
    timeElapsed1 = 0;
  }


  if (outputActive == false && timeElapsed2 > interval2) {  // print data only when not writing the pattern and
                                                            // after a certain interval has passed
    stabilizationOn = digitalRead(stabOnPin);               // read status of stabilization on/off switch and
                                                            // change the display layout accordingly

    u8x8.setFont(u8x8_font_px437wyse700a_2x2_r);  // set large font
    u8x8.setCursor(0, 0);
    u8x8.print(Per_maximum);
    u8x8.print("%   ");

    u8x8.setFont(u8x8_font_chroma48medium8_r);  // set small font for "stab" and "on" or "off"
    u8x8.setCursor(9, 0);
    u8x8.print("Stab");
    u8x8.setCursor(9, 1);
    if (stabilizationOn == true) {
      u8x8.print("On");
    } else {
      u8x8.print("Off");
    }

    u8x8.setFont(u8x8_font_px437wyse700a_2x2_r);  // set large font for the power display
    u8x8.setCursor(0, 2);
    if (stabilizationOn == true) {
      u8x8.print(roundf(powerStabilized));
      u8x8.print(" W   ");
    } else {
      u8x8.print(roundf(powerSelected_actual));

      u8x8.print(" W   ");
    }
    if (outputActive == true) return;

    u8x8.setFont(u8x8_font_chroma48medium8_r);  // set small font for the voltage readouts
    u8x8.setCursor(0, 5);
    u8x8.print(roundf(mainsVoltage_actual));
    u8x8.print(" V actual");
    if (outputActive == true) return;
    u8x8.setCursor(0, 6);
    u8x8.print(mainsVoltage_low, 0);
    u8x8.print(" V stab");


    Serial.print(roundf(percentStabilized));
    Serial.print(" Percent stab.   ");
    Serial.print(  Per_maximum);
    Serial.print(" Percent");

    Serial.print("   Mains:");
    Serial.print(mainsVoltage_actual);
    Serial.print("   powerMax_actual:");
    Serial.print(powerMax_actual);
    Serial.print("   powerStabilized:");
    Serial.print(powerStabilized);
    Serial.print(  " pSa");
    Serial.print(powerSelected_actual);
    Serial.print("  Stab: ");
    Serial.println(stabilizationOn);

    timeElapsed2 = 0;
  }

}  // loop end
//##############################################################################

//###########Function for writing the calculated pattern to the SSR ############
//###########and setting and resetting the outputActive flag ###################
void writePattern() {
  if (outputActive == true) {  // set by the ISR after each falling zerocrossing
    digitalWrite(ssrPin, setoutput);  //Turn SSR on or off as determined by previous pattern calculation
    outputActive = false;             // reset the outputActive flag to prevent from blocking the other activities in the loop
    if(Per_maximum == 0) return;      // do not write to SSR when Per_maximum is set to 0
    //Calculate state of "setoutput" for use after the next falling zerocrossing.
    //This state will be based on the Patterns calculated in Main loop, using the desired percentage of power.
    if (Frame_counter == Maximum + 1 - 5) { digitalWrite(syncPin, 1); }  // set sync signal to HIGH after 95 frames
                                                                         // (100 millisecs before next zerocrossing)
    if (Frame_counter >= (Maximum + 1)) {
      digitalWrite(syncPin, 0);  // set sync signal to LOW when frame counter starts
                                 // in order to allow optional start of the TRMS power meter
      Frame_counter = 1;
      PttnCnt_1st = 0;
      PttnCnt_2nd = 0;
      PttnCnt_3rd = 0;
      PttnCnt_4th = 0;
      PttnCnt_5th = 0;
      setoutput = false;
    }

    Frame_counter++;
    PttnCnt_1st++;  // inc Pttn_1st counter

    if (PttnCnt_1st < (Pttn_1st))  // ie. Pttn_1st is 2 when 37% power is selected
    {
      setoutput = false;  //Turn off   SSR if pttn_1st hasn't arrived
      return;             // and return to loop
    }

    setoutput = true;  // Turn on SSR as PttnCnt_1st has reached  pttn_1st (ie. 2 for 37%)
    PttnCnt_1st = 0;   // Reset Pttn_1st counter
    PttnCnt_2nd++;     // inc Skip counter

    if (Pttn_2nd == 0)  // if no pulses need to be skipped
    {
      setoutput = true;  // leave it turned on
      return;            // and return to loop
    }

    if (PttnCnt_2nd < (Pttn_2nd))  // ie. Pttn_2nd is 3 when 37% power is selected
    {
      setoutput = true;  // keep SSR on if not Skip rate hasn't arrived
      return;
    }

    setoutput = false;  //Turn off SSR as Skip rate has arrived
    PttnCnt_2nd = 0;

    PttnCnt_3rd++;  //inc Keepskip counter
    if (Pttn_3rd == 0) {
      setoutput = false;
      return;
    }
    if (PttnCnt_3rd < (Pttn_3rd)) {
      setoutput = false;
      return;
    }
    setoutput = true;
    PttnCnt_3rd = 0;

    PttnCnt_4th++;
    if (Pttn_4th == 0) {
      setoutput = true;
      return;
    }
    if (PttnCnt_4th < (Pttn_4th)) {
      setoutput = true;  // keep SSR on if not Skip rate hasn't arrived
      return;
    }
    setoutput = false;  //Turn off SSR as Skip rate has arrived
    PttnCnt_4th = 0;
    PttnCnt_5th++;
    if (Pttn_5th == 0) {
      setoutput = true;
      return;
    }
    if (PttnCnt_5th < (Pttn_5th)) {
      setoutput = false;
      return;
    }  // keep SSR on if not Skip rate hasn't arrived

    setoutput = true;  //Turn on SSR as Skip rate has arrived
    PttnCnt_5th = 0;
  }
}


//#############calculate the pulse pattern#########################
void createPattern() {
  if (Per_maximum == 0) Pttn_1st = 0;     //Pttn_1st is first fill
  else Pttn_1st = Maximum / Per_maximum;  //ie turn on every 2nd cycle if 37% is desired  100/37=2

  if (Pttn_1st == 0) Reps_1st = 0;
  else Reps_1st = (Maximum / Pttn_1st);  // i.e 100/2=50 pulsed per cycle

  Skip = Reps_1st - Per_maximum;  // Pttn_2nd runs through first fill and removes certain cycles IE, every 5th
  // i.e for 37% 50-37 = 13
  if (Skip == 0) Pttn_2nd = 0;
  else Pttn_2nd = (Reps_1st / Skip);  // i.e for 37% 50/13=3

  if (Pttn_2nd == 0) Skip_diff = 0;
  else Skip_diff = (Reps_1st / Pttn_2nd);  // i.e for 37% 50/3=16 pulses out of 50 shall be skipped

  Keep_skip = Skip_diff - Skip;  // Pttn_3rd turns back on cycles switched off by Skip-rate
                                 // to correct missing cycles i.e for 37% 16-13=3
  if (Keep_skip == 0) Pttn_3rd = 0;
  else Pttn_3rd = (Skip_diff / Keep_skip);  // i.e for 37% 16/3=5

  if (Pttn_3rd == 0) skip_diff_3 = 0;
  else skip_diff_3 = Skip_diff / Pttn_3rd;  // i.e for 37% 16/5=3

  Loose_keep_skip = skip_diff_3 - Keep_skip;  //Pttn_4th turns off required cycles after Pttn_3rd run
                                              // i.e for 37% 3-3=0
  if (Loose_keep_skip == 0) Pttn_4th = 0;
  else Pttn_4th = skip_diff_3 / Loose_keep_skip;  // i.e for 37% 3/3=1

  if (Pttn_4th == 0) skip_diff_4 = 0;
  else skip_diff_4 = skip_diff_3 / Pttn_4th;

  Restore_Loose_keep_skip = skip_diff_4 - Loose_keep_skip;  //Pttn_5th turns on required cycles after Pttn_4rd run
  if (Restore_Loose_keep_skip == 0) Pttn_5th = 0;
  else Pttn_5th = skip_diff_4 / Restore_Loose_keep_skip;
}

//########Interrupt Service Routine for the zerocrossing detector ##############
ISR(PCINT0_vect)  // ISR for pin change interrupt on port B pin 12 (Zerocrossing detected )
{
  if (digitalRead(zerocross) == false) {  // react only when pinchange was falling edge
                                          // otherwise do nothing and return
    outputActive = true;                  // enable the routine for writing the output pattern
                                          // to the SSR with any falling edge
  }
}

I am really happy with that power stabilization feature. It keeps the heating power stable and independent from changes of the mains voltage.
The indication of the heating power in Watts is accurate enough, so I do not need a separate RMS power meter.
Thanks again to Yummyrum for inventing this burstfire controller solution for Arduino microcontrollers.
I hope that this was the last correction of errors, caused by me.
User avatar
harold01
Bootlegger
Posts: 122
Joined: Wed Feb 22, 2023 2:24 pm

Re: Yummy's DIY Burst fire controller project

Post by harold01 »

Yep my head hurts too
So yummy was right, is my take on this
User avatar
Yummyrum
Global moderator
Posts: 8641
Joined: Sat Jul 06, 2013 2:23 am
Location: Fraser Coast QLD Aussie

Re: Yummy's DIY Burst fire controller project

Post by Yummyrum »

harold01 wrote: Fri Oct 18, 2024 4:20 am Yep my head hurts too
So yummy was right, is my take on this
Yeah ,it was never going to be a project for everyone Harold …. But it works and simulates what Auber controllers do in the way it regulates power . And while average Joe blow will undoubtably just buy an Auber , I did it so folk like Kennstminet can “roll their own” and customise it .

Kenn has done just that and taken this to the next level and added power regulation to account for variations in local supply and included a power meter . That is something the Auber controllers can’t do .

So Harold , you could easily use this controller to feed a three phase system . Just add another two SSRs for the other two phases and you are good …. and a small code adjustment to three times the power reading .
User avatar
Yummyrum
Global moderator
Posts: 8641
Joined: Sat Jul 06, 2013 2:23 am
Location: Fraser Coast QLD Aussie

Re: Yummy's DIY Burst fire controller project

Post by Yummyrum »

Kenn , I am so sorry.
I was aware of that original error with full power at 0% and I had fixed it but I posted an earlier code version that had it :oops:
I had addressed it back here .
viewtopic.php?p=7787704#p7787704

Regardless , you found another work-a-round . :thumbup:….as they say , many ways to skin a cat :wink:

Glad you have taken this to the next level .
Heck , I might be copying your code and making one fir myself . :thumbup:…thanks
kennstminet
Bootlegger
Posts: 135
Joined: Thu Aug 30, 2018 12:47 pm
Location: Not there

Re: Yummy's DIY Burst fire controller project

Post by kennstminet »

Yummy
I am sorry that I had ignored your code fix.
It seems that for some reason, I had a partial memory erasure. May have to do with our main hobby here. :lol:
zukram
Novice
Posts: 28
Joined: Tue Mar 19, 2024 5:51 am

Re: Yummy's DIY Burst fire controller project

Post by zukram »

For 3-phase you should also add a 60 and 120 degree delay for L2 and L3.. :-)
User avatar
Yummyrum
Global moderator
Posts: 8641
Joined: Sat Jul 06, 2013 2:23 am
Location: Fraser Coast QLD Aussie

Re: Yummy's DIY Burst fire controller project

Post by Yummyrum »

zukram wrote: Thu Oct 24, 2024 12:05 am For 3-phase you should also add a 60 and 120 degree delay for L2 and L3.. :-)
I used to think that two zukram , but if you’re trigger pulse is almost half a cycle , its long enough to trigger the other two phases .

BTW , thats what Auber does . And they sell
A three phase SSR module that can be controlled
by a DSPR1 .Or just use three individual SSRs
zukram
Novice
Posts: 28
Joined: Tue Mar 19, 2024 5:51 am

Re: Yummy's DIY Burst fire controller project

Post by zukram »

Yes. hm. that would work.
Post Reply