Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 [email protected] @ 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 [email protected]
This software is released under an MIT license. See the attached LICENSE file for details.
217 changes: 175 additions & 42 deletions src/Dimmer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 [email protected]
* This software is released under the MIT license. See the attached LICENSE file for details.
*/

#include "Arduino.h"
#include "Dimmer.h"
#include <util/atomic.h>
////#include <util/atomic.h>

// Timer configuration macros
// Timer configuration macros_TIMER(X)
#define _TCNT(X) TCNT ## X
#define TCNT(X) _TCNT(X)
#define _TCCRxA(X) TCCR ## X ## A
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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++) {
Expand All @@ -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),
Expand All @@ -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;
}
}
Expand All @@ -172,19 +291,24 @@ void Dimmer::on() {
}

void Dimmer::toggle() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
//ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
noInterrupts();
lampState = !lampState;
}
interrupts();
//}
}

bool Dimmer::getState() {
return lampState;
}

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) {
Expand All @@ -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();
Expand All @@ -209,7 +334,7 @@ void Dimmer::set(uint8_t value) {
}
lampValue = value;
}
}
interrupts();
}

void Dimmer::set(uint8_t value, bool on) {
Expand All @@ -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() {
Expand Down Expand Up @@ -282,3 +411,7 @@ void Dimmer::zeroCross() {
}
}
}

uint8_t Dimmer::getAcFrequency(){
return acFreq;
}
Loading