diff --git a/README.rst b/README.rst index 954065f..a11700f 100644 --- a/README.rst +++ b/README.rst @@ -29,9 +29,15 @@ The following examples_ are provided: .. _WaveLamps: https://github.com/circuitar/Dimmer/blob/master/examples/WaveLamps/WaveLamps.ino .. _CountMode: https://github.com/circuitar/Dimmer/blob/master/examples/CountMode/CountMode.ino ----- +Updated by hamidsaffari@yahoo.com @ 2020: + +Supported Arduino_STM32 as well. https://github.com/rogerclarkmelbourne/Arduino_STM32 + +Supported Arduino_Core_STM32 too. https://github.com/stm32duino/Arduino_Core_STM32 + +Added Auto_Main_frequency_detection option and getAcFrequency() to give you the AC main frequency. Copyright (c) 2015 Circuitar All rights reserved. - +Updated @ 2020 by HamidSaffari@yahoo.com This software is released under an MIT license. See the attached LICENSE file for details. diff --git a/src/Dimmer.cpp b/src/Dimmer.cpp index c2ca3ce..dd48f44 100644 --- a/src/Dimmer.cpp +++ b/src/Dimmer.cpp @@ -2,14 +2,15 @@ * This is a library to control the intensity of dimmable AC lamps or other AC loads using triacs. * * Copyright (c) 2015 Circuitar + * Updated @ 2020 by HamidSaffari@yahoo.com * This software is released under the MIT license. See the attached LICENSE file for details. */ #include "Arduino.h" #include "Dimmer.h" -#include +////#include -// Timer configuration macros +// Timer configuration macros_TIMER(X) #define _TCNT(X) TCNT ## X #define TCNT(X) _TCNT(X) #define _TCCRxA(X) TCCR ## X ## A @@ -22,6 +23,12 @@ #define OCRxA(X) _OCRxA(X) #define _TIMER_COMPA_VECTOR(X) TIMER ## X ## _COMPA_vect #define TIMER_COMPA_VECTOR(X) _TIMER_COMPA_VECTOR(X) +#define _Timer(X) Timer ## X +#define Timer(X) _Timer(X) +#define _TIM(X) TIM ## X +#define TIM(X) _TIM(X) +#define _NVIC_TIMER(X) NVIC_TIMER ## X +#define NVIC_TIMER(X) _NVIC_TIMER(X) // Helper macros #define DIMMER_PULSES_TO_VALUE(pulses, bits) ((uint16_t)(pulses) * 100 / (bits)) @@ -63,8 +70,19 @@ static Dimmer* dimmmers[DIMMER_MAX_TRIAC]; // Pointers to all registered dim static uint8_t dimmerCount = 0; // Number of registered dimmer objects // Triac pin and timing variables. Using global arrays to make ISR fast. -static volatile uint8_t* triacPinPorts[DIMMER_MAX_TRIAC]; // Triac ports for registered dimmers -static uint8_t triacPinMasks[DIMMER_MAX_TRIAC]; // Triac pin mask for registered dimmers +#if defined(ARDUINO_ARCH_AVR) + static volatile uint8_t* triacPinPorts[DIMMER_MAX_TRIAC]; // Triac ports for registered dimmers + static uint8_t triacPinMasks[DIMMER_MAX_TRIAC]; // Triac pin mask for registered dimmers +#elif defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F4) || defined(ARDUINO_ARCH_STM32) + static volatile uint32_t* triacPinPorts[DIMMER_MAX_TRIAC]; // Triac ports for registered dimmers + static uint32_t triacPinMasks[DIMMER_MAX_TRIAC]; // Triac pin mask for registered dimmers +#endif + +#if defined(ARDUINO_ARCH_STM32) + HardwareTimer MyTim = HardwareTimer(TIM(DIMMER_TIMER)); + +#endif + static uint8_t triacTimes[DIMMER_MAX_TRIAC]; // Triac time for registered dimmers // Timer ticks since zero crossing @@ -73,12 +91,50 @@ static uint8_t tmrCount = 0; // Global state variables bool Dimmer::started = false; // At least one dimmer has started +//================== added by Hamid ================== + +bool Auto_Main_frequency_detection=false; +volatile uint8_t sample_for_Main_frequency_detection; +unsigned long lastTime_for_Main_frequency_detection; +volatile unsigned long frequency_detection_timings_sum; + +//==================================================== + // Zero cross interrupt void callZeroCross() { + + //================== added by Hamid =================== + if(Auto_Main_frequency_detection){ + + if(sample_for_Main_frequency_detection <= NUMBER_OF_SAMLPLES_FOR_MAIN_FREQUENCY_DETECTION){ + + const long time = micros(); + const unsigned long duration = time - lastTime_for_Main_frequency_detection; + + if (duration <= (1000.0/MIN_INPUT_FREQUENCY)*1000.0/2.0 && duration >= (1000.0/MAX_INPUT_FREQUENCY)*1000.0/2.0){ + if(sample_for_Main_frequency_detection!=0) frequency_detection_timings_sum += duration; // dump first one + sample_for_Main_frequency_detection++; + } + lastTime_for_Main_frequency_detection = time; + } + return; + } + //==================================================== + + // Clear the timer and enable nested interrupts so that the timer can be updated while this ISR is executed - TCNT(DIMMER_TIMER) = 0; + #if defined(ARDUINO_ARCH_AVR) + TCNT(DIMMER_TIMER) = 0; + //sei(); + #elif defined(ARDUINO_ARCH__STM32F1) || defined(ARDUINO_ARCH__STM32F4) + //Timer(DIMMER_TIMER).setCount(0); //The new count value to set. If this value exceeds the timer’s overflow value, it is truncated to the overflow value. + Timer(DIMMER_TIMER).refresh(); //This will reset the counter to 0 in upcounting mode (the default). It will also update the timer’s prescaler and overflow, if you have set them up to be changed using HardwareTimer::setPrescaleFactor() or HardwareTimer::setOverflow(). + #elif defined(ARDUINO_ARCH_STM32) + MyTim.setCount(0); + #endif + + interrupts(); tmrCount = 0; - sei(); // Turn off all triacs and disable further triac activation before anything else for (uint8_t i = 0; i < dimmerCount; i++) { @@ -98,24 +154,35 @@ void callZeroCross() { } // Timer interrupt -ISR(TIMER_COMPA_VECTOR(DIMMER_TIMER)) { - // Increment ticks - if (tmrCount < 254) { - tmrCount++; - } +#if defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F4) + void onTimerISR() + +#elif defined(ARDUINO_ARCH_STM32) + void onTimerISR(HardwareTimer*) + +#elif defined(ARDUINO_ARCH_AVR) + ISR(TIMER_COMPA_VECTOR(DIMMER_TIMER)) + +#endif + { + // Increment ticks + if (tmrCount < 254) { + tmrCount++; + } - // Process each registered triac and turn it on if needed - for (uint8_t i = 0; i < dimmerCount; i++) { - if (tmrCount == triacTimes[i]) { - *triacPinPorts[i] |= triacPinMasks[i]; + // Process each registered triac and turn it on if needed + for (uint8_t i = 0; i < dimmerCount; i++) { + if (tmrCount == triacTimes[i]) { + *triacPinPorts[i] |= triacPinMasks[i]; + } } } -} // Constructor -Dimmer::Dimmer(uint8_t pin, uint8_t mode, double rampTime, uint8_t freq) : - triacPin(pin), - operatingMode(mode), +Dimmer::Dimmer(uint8_t out_dimmer_pin, uint8_t zc_dimmer_pin, uint8_t mode, double rampTime, uint8_t freq) : + triacPin(out_dimmer_pin), + zc_pin(zc_dimmer_pin), + operatingMode(mode), lampState(false), lampValue(0), rampStartValue(0), @@ -125,40 +192,92 @@ Dimmer::Dimmer(uint8_t pin, uint8_t mode, double rampTime, uint8_t freq) : pulsesLow(0), pulseCount(0), pulsesUsed(0), - acFreq(freq) { + _rampTime(rampTime), + acFreq(freq) { + if (dimmerCount < DIMMER_MAX_TRIAC) { // Register dimmer object being created dimmerIndex = dimmerCount; dimmmers[dimmerCount++] = this; - triacPinPorts[dimmerIndex] = portOutputRegister(digitalPinToPort(pin)); - triacPinMasks[dimmerIndex] = digitalPinToBitMask(pin); + triacPinPorts[dimmerIndex] = portOutputRegister(digitalPinToPort(out_dimmer_pin)); + triacPinMasks[dimmerIndex] = digitalPinToBitMask(out_dimmer_pin); } - if (mode == DIMMER_RAMP) { + if (mode == DIMMER_RAMP && freq!=false) { setRampTime(rampTime); } } -void Dimmer::begin(uint8_t value, bool on) { +void Dimmer::begin(uint8_t value, bool on ) { // Initialize lamp state and value set(value, on); // Initialize triac pin pinMode(triacPin, OUTPUT); digitalWrite(triacPin, LOW); - + if (!started) { + + if (acFreq == false) Auto_Main_frequency_detection = true; + // Start zero cross circuit if not started yet - pinMode(DIMMER_ZERO_CROSS_PIN, INPUT); - attachInterrupt(DIMMER_ZERO_CROSS_INTERRUPT, callZeroCross, RISING); - + pinMode(zc_pin, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(zc_pin), callZeroCross, RISING); + + //================== added by Hamid ================= + if(Auto_Main_frequency_detection){ + + while(sample_for_Main_frequency_detection <= NUMBER_OF_SAMLPLES_FOR_MAIN_FREQUENCY_DETECTION){ + // Zerocross-Interrupt doing his job + } + + //ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { //Creates a block of code that is guaranteed to be executed atomically. Upon entering the block the Global Interrupt Status flag in SREG is disabled, and re-enabled upon exiting the block from any exit path. + noInterrupts(); // go atomic level - avoid interrupts to surely get the previously received data + + /* If the volatile variable is bigger than a byte (e.g. a 16 bit int or a 32 bit long), then the microcontroller can not read it in one step, because it is an 8 bit microcontroller. This means that while your main code section (e.g. your loop) reads the first 8 bits of the variable, the interrupt might already change the second 8 bits. This will produce random values for the variable. + Remedy: + While the variable is read, interrupts need to be disabled, so they can’t mess with the bits, while they are read. There are several ways to do this: + */ + float Main_frequency; + Main_frequency = (1/((frequency_detection_timings_sum/NUMBER_OF_SAMLPLES_FOR_MAIN_FREQUENCY_DETECTION)/1000000.0))/2.0 ;// ( /2) because zerocross occur in half cycle + acFreq = round(Main_frequency); + Auto_Main_frequency_detection= false;// must be after all to execute "return" in ISR + + if (operatingMode == DIMMER_RAMP ) { + setRampTime(_rampTime); + } + + interrupts(); // let interrupt do its job + //} + } + + //====================================================== // Setup timer to fire every 50us @ 60Hz - TCNT(DIMMER_TIMER) = 0; // Clear timer + + #if defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH__STM32F4) + + Timer(DIMMER_TIMER).setMode(TIMER_CH1, TIMER_OUTPUTCOMPARE); + Timer(DIMMER_TIMER).setPeriod(50*(60.0/acFreq)); // in microseconds + Timer(DIMMER_TIMER).setCompare(TIMER_CH1, 1); // Interrupt 1 count after each update, overflow might be small + Timer(DIMMER_TIMER).attachInterrupt(TIMER_CH1, onTimerISR); + //nvic_irq_set_priority(NVIC_TIMER(DIMMER_TIMER), 15);// interrupt must not be preempted. highest interrupt priority is 0. All other interrupts have been initialized to priority level 16. See nvic_init(). + + #elif defined(ARDUINO_ARCH_STM32) + + MyTim.setMode(2, TIMER_OUTPUT_COMPARE); + MyTim.setOverflow(50*(60.0/acFreq), MICROSEC_FORMAT); + MyTim.attachInterrupt(onTimerISR); + MyTim.resume(); + + #elif defined(ARDUINO_ARCH_AVR) + + TCNT(DIMMER_TIMER) = 0; // Clear timer TCCRxA(DIMMER_TIMER) = TCCRxA_VALUE; // Timer config byte A TCCRxB(DIMMER_TIMER) = TCCRxB_VALUE; // Timer config byte B TIMSKx(DIMMER_TIMER) = 0x02; // Timer Compare Match Interrupt Enable - OCRxA(DIMMER_TIMER) = 100 * 60 / acFreq - 1; // Compare value (frequency adjusted) - + OCRxA(DIMMER_TIMER) = 100 * 60.0 / acFreq - 1; // Compare value (frequency adjusted) + + #endif started = true; } } @@ -172,9 +291,11 @@ void Dimmer::on() { } void Dimmer::toggle() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + //ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + noInterrupts(); lampState = !lampState; - } + interrupts(); + //} } bool Dimmer::getState() { @@ -182,9 +303,12 @@ bool Dimmer::getState() { } uint8_t Dimmer::getValue() { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - return rampStartValue + ((int32_t) lampValue - rampStartValue) * rampCounter / rampCycles; - } + //ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + noInterrupts(); + uint8_t val = rampStartValue + ((int32_t) lampValue - rampStartValue) * rampCounter / rampCycles ; + interrupts(); + return val; + //} } void Dimmer::set(uint8_t value) { @@ -196,7 +320,8 @@ void Dimmer::set(uint8_t value) { value = minValue; } - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + //ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + noInterrupts(); if (value != lampValue) { if (operatingMode == DIMMER_RAMP) { rampStartValue = getValue(); @@ -209,7 +334,7 @@ void Dimmer::set(uint8_t value) { } lampValue = value; } - } + interrupts(); } void Dimmer::set(uint8_t value, bool on) { @@ -224,19 +349,23 @@ void Dimmer::setMinimum(uint8_t value) { minValue = value; - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + //ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + noInterrupts(); if (lampValue < minValue) { set(value); } - } + interrupts(); + //} } void Dimmer::setRampTime(double rampTime) { - ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + //ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + noInterrupts(); rampTime = rampTime * 2 * acFreq; rampCycles = rampTime > 0xFFFF ? 0xFFFF : rampTime; rampCounter = rampCycles; - } + interrupts(); + //} } void Dimmer::zeroCross() { @@ -282,3 +411,7 @@ void Dimmer::zeroCross() { } } } + +uint8_t Dimmer::getAcFrequency(){ + return acFreq; +} diff --git a/src/Dimmer.h b/src/Dimmer.h index 1e7f9c1..3867d40 100644 --- a/src/Dimmer.h +++ b/src/Dimmer.h @@ -2,6 +2,7 @@ * This is a library to control the intensity of dimmable AC lamps or other AC loads using triacs. * * Copyright (c) 2015 Circuitar + * Updated @ 2020 by HamidSaffari@yahoo.com * This software is released under the MIT license. See the attached LICENSE file for details. */ @@ -11,7 +12,7 @@ /** * Maximum number of triacs that can be used. Can be decreased to save RAM. */ -#define DIMMER_MAX_TRIAC 10 +#define DIMMER_MAX_TRIAC 1 /** * Timer to use for control of triac timing. @@ -20,6 +21,8 @@ #define DIMMER_TIMER 4 #elif defined(__AVR_ATmega32U4__) #define DIMMER_TIMER 3 +#elif defined(ARDUINO_ARCH__STM32F1) || defined(ARDUINO_ARCH__STM32F4) || defined(ARDUINO_ARCH_STM32) +#define DIMMER_TIMER 3 #else #define DIMMER_TIMER 2 #endif @@ -32,8 +35,8 @@ * * @see https://www.arduino.cc/en/Reference/attachInterrupt for more information. */ -#define DIMMER_ZERO_CROSS_PIN 2 -#define DIMMER_ZERO_CROSS_INTERRUPT 0 +//#define DIMMER_ZERO_CROSS_PIN 2 +//#define DIMMER_ZERO_CROSS_INTERRUPT 0 /** * Possible operating modes for the dimmer library. @@ -42,6 +45,14 @@ #define DIMMER_RAMP 1 #define DIMMER_COUNT 2 +//===== added by Hamid for Auto_Main_frequency_detection ================ + +#define NUMBER_OF_SAMLPLES_FOR_MAIN_FREQUENCY_DETECTION 25 +#define MAX_INPUT_FREQUENCY 80 // Hz +#define MIN_INPUT_FREQUENCY 30 // Hz + +//================================================================ + /** * A dimmer channel. * @@ -65,7 +76,7 @@ class Dimmer { * * @see begin() */ - Dimmer(uint8_t pin, uint8_t mode = DIMMER_NORMAL, double rampTime = 1.5, uint8_t freq = 60); + Dimmer(uint8_t pin, uint8_t zc_dimmer_pin, uint8_t mode = DIMMER_NORMAL, double rampTime = 1.5, uint8_t freq = 60); //if freq=false then Auto_Main_frequency_detection /** * Initializes the module. @@ -135,11 +146,15 @@ class Dimmer { * @param value the ramp time. Maximum is 2^16 / (2 * AC frequency) */ void setRampTime(double rampTime); + + uint8_t getAcFrequency(); + private: static bool started; uint8_t dimmerIndex; uint8_t triacPin; + uint8_t zc_pin; uint8_t operatingMode; bool lampState; uint8_t lampValue; @@ -152,11 +167,14 @@ class Dimmer { uint8_t pulsesUsed; uint64_t pulsesHigh; uint64_t pulsesLow; + double _rampTime; void zeroCross(); friend void callTriac(); friend void callZeroCross(); + friend void onTimerISR(); + }; #endif