diff --git a/README.md b/README.md index 2fa3a1f..639c7e9 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,41 @@ The phase offsets are calculated based on the _measured_ mains frequency, so this code will work regardless of 50/60Hz or any other frequency. This includes correcting for any inaccuracies in the arduino's oscillator or the mains frequency. -This library was developed specifically to control a Krida 2 CH Dimmer +This library was developed specifically for the Krida 2 CH Dimmer ([amazon](https://www.amazon.com/Dimmer-Module-Controller-Arduino-Raspberry/dp/B06Y1GVG26), [alibaba](https://mdwdz.en.alibaba.com/product/60670737878-804998378/2CH_AC_LED_Light_Dimmer_Module_Controller_Board.html), [inmojo](http://www.inmojo.com/store/krida-electronics/item/2-channel-ac-led-bulb-dimmer-module-v2/)), - but it should work with other phase-control dimming circuits that output a positive edge on their sync signal. + and has been tested to work with the RobotDyn AC Dimmer + ([robotdyn](https://robotdyn.com/ac-light-dimmer-module-1-channel-3-3v-5v-logic-ac-50-60hz-220v-110v.html)), + and should work fine with other phase-control dimming circuits that output a positive edge on their sync signal. See [the example](examples/basic_example/basic_example.ino) for an example of how to use the library. The library methods themselves are documented in [the library header](src/TriacDimmer.h). -This library **requires** the use of pins 8, 9, and 10. -Pin 8 _must_ be used as the sync input, and pins 9 and 10 _must_ be used as the channel outputs. +This library **requires** the use of certain pins. +Pin 8 _must_ be used as the sync input, and pins 9 and 10 are the only pins that can be used as channel outputs. This library _will not work_ on any other pins, period. -![fritzing diagram](fritzing.png) \ No newline at end of file +![fritzing diagram](fritzing.png) + +## Flickering, and How to Fix It + +If you experience issues with flickering, there are a handful of parameters you can pass to `begin` that can be adjusted depending on what sort of flickering you have. +By default, with no arguments the library uses these values as defaults: + +```cpp +TriacDimmer::begin(pulse_length = 20, min_trigger = 2000, on_thresh = 2, off_thresh = 0.01) +``` + +First off, if you experience flickering regardless of the brightness value you set, increase `pulse_length` from the default `20` to a larger value like `50` or `100` until `TriacDimmer::setBrightness(pin, 0.5);` results in a stable glow. + +Once it's stable at 0.5, set the brightness 1.0 (`TriacDimmer::setBrightness(pin, 1.0);`) and check for flickering. +There shouldn't be any, but if there is you can increase `min_trigger` from the default `2000` to perhaps `3000` or `4000` until the flickering stops. +If you're still experiencing flickering no matter how large `min_trigger` is, you can also try setting `on_thresh` to below the highest brightness level that causes flickering. This shouldn't normally be necessary though, as adjusting `min_trigger` should normally be enough. + +The last step is to figure out the lowest brightness value can sustain without flickering. +By default the library is set to cut off completely for brightness values smaller than `0.01`, but if you still see flickering at `0.015` or `0.02` you can try setting `off_thresh` to a value that's larger than that. + +If you've tried all of these steps and you still get flickering, consider opening an issue. +Make sure to include as much information about your setup as you can, including the specific dimmer board you're using. +Also, if you have access to an oscilliscope, screenshots are always helpful in trying to diagnose flickering. diff --git a/examples/basic_example/basic_example.ino b/examples/basic_example/basic_example.ino index 6c9994c..2dffe3a 100644 --- a/examples/basic_example/basic_example.ino +++ b/examples/basic_example/basic_example.ino @@ -38,31 +38,31 @@ unsigned char channel_1 = 9; // channel 1 pin unsigned char channel_2 = 10; // channel 2 pin void setup() { - //initialize the dimmer library. We want + // initialize the dimmer library. TriacDimmer::begin(); } void loop() { - //gradually increase brightness over time - for(float brightness = 0.01; brightness < 0.99; brightness += 0.01){ + // gradually increase brightness over time + for(float brightness = 0.00; brightness < 1.00; brightness += 0.01){ - //set channel 1 to the brightness value: + // set channel 1 to the brightness value: TriacDimmer::setBrightness(channel_1, brightness); - //invert brightness for channel 2: + // invert brightness for channel 2: TriacDimmer::setBrightness(channel_2, 1 - brightness); delay(20); } - //and back down - decrease brightness over time - for(float brightness = 0.99; brightness > 0.01; brightness -= 0.01){ + // and back down - decrease brightness over time + for(float brightness = 1.00; brightness > 0.00; brightness -= 0.01){ - //set channel 1 to the brightness value: + // set channel 1 to the brightness value: TriacDimmer::setBrightness(channel_1, brightness); - //invert brightness for channel 2: - TriacDimmer::setBrightness(channel_2, brightness); + // invert brightness for channel 2: + TriacDimmer::setBrightness(channel_2, 1 - brightness); delay(20); } diff --git a/keywords.txt b/keywords.txt index a20ca10..60f064a 100644 --- a/keywords.txt +++ b/keywords.txt @@ -15,6 +15,7 @@ begin KEYWORD2 end KEYWORD2 setBrightness KEYWORD2 getCurrentBrightness KEYWORD2 +disable KEYWORD2 ###################################### # Constants/defines (LITERAL1) diff --git a/library.properties b/library.properties index 9eedadb..8fd2b23 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=TriacDimmer -version=1.0.3 +version=1.1.0 author=Anson Mansfield maintainer=Anson Mansfield sentence=A library for controlling a triac dimmer. -paragraph=Abuses the PWM capabilities of Timer 1 to offload all timing code from the CPU. +paragraph=Uses the advanced capabilities of the Timer 1 perhipheral to offload all timing code from the CPU, resulting in much more accurate timing than is possible normally. category=Device Control url=https://github.com/AJMansfield/TriacDimmer architectures=avr diff --git a/src/TriacDimmer.cpp b/src/TriacDimmer.cpp index c8ea4bd..769b31f 100644 --- a/src/TriacDimmer.cpp +++ b/src/TriacDimmer.cpp @@ -1,16 +1,33 @@ #include "TriacDimmer.h" #include #include +#include -volatile uint16_t TriacDimmer::detail::pulse_length; -volatile uint16_t TriacDimmer::detail::period = 16667; +uint16_t TriacDimmer::detail::pulse_length; +uint16_t TriacDimmer::detail::min_trigger; +float TriacDimmer::detail::on_thresh; +float TriacDimmer::detail::off_thresh; + +volatile bool TriacDimmer::detail::ch_A_en; volatile uint16_t TriacDimmer::detail::ch_A_up; volatile uint16_t TriacDimmer::detail::ch_A_dn; +volatile uint16_t ch_A_dn_buf; + +volatile bool TriacDimmer::detail::ch_B_en; volatile uint16_t TriacDimmer::detail::ch_B_up; volatile uint16_t TriacDimmer::detail::ch_B_dn; +volatile uint16_t ch_B_dn_buf; + +uint16_t last_icr = 0; +volatile uint16_t TriacDimmer::detail::period = 16667; + -void TriacDimmer::begin(uint16_t pulse_length){ +void TriacDimmer::begin(uint16_t pulse_length, uint16_t min_trigger, float on_thresh, float off_thresh){ + // Don't need atomic writes since the ISRs haven't started yet. TriacDimmer::detail::pulse_length = pulse_length; + TriacDimmer::detail::min_trigger = min_trigger; + TriacDimmer::detail::on_thresh = on_thresh; + TriacDimmer::detail::off_thresh = off_thresh; TCCR1A = 0; TCCR1B = _BV(ICNC1) //input capture noise cancel @@ -18,8 +35,6 @@ void TriacDimmer::begin(uint16_t pulse_length){ | _BV(CS11); // /8 prescaler pinMode(8, INPUT); - pinMode(9, OUTPUT); - pinMode(10, OUTPUT); TIFR1 = _BV(ICF1); //clear IC interrupt flag TIMSK1 = _BV(ICIE1); //enable input capture interrupt @@ -35,16 +50,37 @@ void TriacDimmer::end(){ void TriacDimmer::setBrightness(uint8_t pin, float value){ assert(pin == 9 || pin == 10); + if (value > TriacDimmer::detail::on_thresh) { + digitalWrite(pin, HIGH); + TriacDimmer::disable(pin); + } else if (value < TriacDimmer::detail::off_thresh) { + digitalWrite(pin, LOW); + TriacDimmer::disable(pin); + } else { + if ((pin & 0x01) == 0x01){ // if (pin == 9){ + TriacDimmer::detail::setChannelA(1 - value); + TriacDimmer::detail::ch_A_en = true; + } else { // if (pin == 10){ + TriacDimmer::detail::setChannelB(1 - value); + TriacDimmer::detail::ch_B_en = true; + } + } + pinMode(pin, OUTPUT); // only set to output once configured. +} + +void TriacDimmer::disable(uint8_t pin) { + assert(pin == 9 || pin == 10); + if ((pin & 0x01) == 0x01){ // if (pin == 9){ - TriacDimmer::detail::setChannelA(1 - value); + TriacDimmer::detail::disableChannelA(); } else { // if (pin == 10){ - TriacDimmer::detail::setChannelB(1 - value); + TriacDimmer::detail::disableChannelB(); } } float TriacDimmer::getCurrentBrightness(uint8_t pin){ assert(pin == 9 || pin == 10); - + if ((pin & 0x01) == 0x01){ // if (pin == 9){ return 1 - TriacDimmer::detail::getChannelA(); } else { // if (pin == 10){ @@ -53,36 +89,77 @@ float TriacDimmer::getCurrentBrightness(uint8_t pin){ } void TriacDimmer::detail::setChannelA(float value){ - TriacDimmer::detail::ch_A_up = TriacDimmer::detail::period * value; - TriacDimmer::detail::ch_A_dn = TriacDimmer::detail::ch_A_up + TriacDimmer::detail::pulse_length; + uint16_t p, u, d; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + // Don't need atomic read for pulse_length or min_trigger since they're not updated from an ISR. + p = TriacDimmer::detail::period; + } + u = p * value; + d = constrain(u + TriacDimmer::detail::pulse_length, TriacDimmer::detail::min_trigger, p); + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + TriacDimmer::detail::ch_A_up = u; + TriacDimmer::detail::ch_A_dn = d; + } } void TriacDimmer::detail::setChannelB(float value){ - TriacDimmer::detail::ch_B_up = TriacDimmer::detail::period * value; - TriacDimmer::detail::ch_B_dn = TriacDimmer::detail::ch_B_up + TriacDimmer::detail::pulse_length; + uint16_t p, u, d; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + // Don't need atomic read for pulse_length or min_trigger since they're not updated from an ISR. + p = TriacDimmer::detail::period; + } + u = p * value; + d = constrain(u + TriacDimmer::detail::pulse_length, TriacDimmer::detail::min_trigger, p); + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + TriacDimmer::detail::ch_B_up = u; + TriacDimmer::detail::ch_B_dn = d; + } } + float TriacDimmer::detail::getChannelA(){ - return (float)TriacDimmer::detail::ch_A_up / TriacDimmer::detail::period; + uint16_t p, u; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + p = TriacDimmer::detail::period; + u = TriacDimmer::detail::ch_A_up; + } + return (float)p / u; } + float TriacDimmer::detail::getChannelB(){ - return (float)TriacDimmer::detail::ch_B_up / TriacDimmer::detail::period; + uint16_t p, u; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + p = TriacDimmer::detail::period; + u = TriacDimmer::detail::ch_B_up; + } + return (float)p / u; +} + +void TriacDimmer::detail::disableChannelA(){ + TriacDimmer::detail::ch_A_en = false; + TCCR1A &=~ _BV(COM1A0) | _BV(COM1A1); } +void TriacDimmer::detail::disableChannelB(){ + TriacDimmer::detail::ch_B_en = false; + TCCR1A &=~ _BV(COM1B0) | _BV(COM1B1); +} ISR(TIMER1_CAPT_vect){ TIMSK1 &=~ (_BV(OCIE1A) | _BV(OCIE1B)); //clear interrupts, in case they haven't run yet - TCCR1A &=~ (_BV(COM1A1) | _BV(COM1B1)); + TCCR1A &=~ (_BV(COM1A0) | _BV(COM1B0)); TCCR1C = _BV(FOC1A) | _BV(FOC1B); //ensure outputs are properly cleared OCR1A = ICR1 + TriacDimmer::detail::ch_A_up; OCR1B = ICR1 + TriacDimmer::detail::ch_B_up; + ch_A_dn_buf = TriacDimmer::detail::ch_A_dn; + ch_B_dn_buf = TriacDimmer::detail::ch_B_dn; - TCCR1A |= _BV(COM1A0) | _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1); //set OC1x on compare match + TCCR1A |= + ( TriacDimmer::detail::ch_A_en ? _BV(COM1A0) | _BV(COM1A1) : 0 ) | + ( TriacDimmer::detail::ch_B_en ? _BV(COM1B0) | _BV(COM1B1) : 0 ); //set OC1x on compare match, only if enabled TIFR1 = _BV(OCF1A) | _BV(OCF1B); //clear compare match flags TIMSK1 |= _BV(OCIE1A) | _BV(OCIE1B); //enable input capture and compare match interrupts - - static uint16_t last_icr = 0; TriacDimmer::detail::period = ICR1 - last_icr; last_icr = ICR1; @@ -97,9 +174,9 @@ ISR(TIMER1_CAPT_vect){ ISR(TIMER1_COMPA_vect){ TIMSK1 &=~ _BV(OCIE1A); //disable match interrupt - TCCR1A |= _BV(COM1A1); //clear OC1x on compare match + TCCR1A &=~ _BV(COM1A0); //clear OC1x on compare match - OCR1A = ICR1 + TriacDimmer::detail::ch_A_dn; + OCR1A = last_icr + ch_A_dn_buf; if((signed)(TCNT1 - OCR1A) >= 0){ TCCR1C = _BV(FOC1A); //interrupt ran late, trigger match manually @@ -109,9 +186,9 @@ ISR(TIMER1_COMPA_vect){ ISR(TIMER1_COMPB_vect){ TIMSK1 &=~ _BV(OCIE1B); //disable match interrupt - TCCR1A |= _BV(COM1B1); //clear OC1x on compare match + TCCR1A &=~ _BV(COM1B0); //clear OC1x on compare match - OCR1B = ICR1 + TriacDimmer::detail::ch_B_dn; + OCR1B = last_icr + ch_B_dn_buf; if((signed)(TCNT1 - OCR1B) >= 0){ TCCR1C = _BV(FOC1B); //interrupt ran late, trigger match manually diff --git a/src/TriacDimmer.h b/src/TriacDimmer.h index fa5f80e..9e0a521 100644 --- a/src/TriacDimmer.h +++ b/src/TriacDimmer.h @@ -21,9 +21,12 @@ namespace TriacDimmer { /** * @brief Initializes the library. * @param pulse_length How long the trigger pulses should be, in half-microseconds. + * @param min_trigger Minimum offset from beginning of phase to end of trigger pulse to ensure triac latches. + * @param on_thresh Brightness threshold where the light will be turned on completely. >1 means disabled. + * @param off_thresh Brightness threshold where the light will be turned off completely. <0 means disabled. * This method initializes the library, setting up the timer and enabling the corresponding interrupts. */ - void begin(uint16_t pulse_length = 20); + void begin(uint16_t pulse_length = 20, uint16_t min_trigger = 2000, float on_thresh = 2.0, float off_thresh = 0.01); /** * @brief Stops the library @@ -35,17 +38,22 @@ namespace TriacDimmer { * @brief Sets the current brightness. * @param pin The pin controlling the desired channel. Only pins 9 and 10 are supported. * @param value The desired brightness, from 0.0 to 1.0. - * This method sets the brightness for the channel controlled by the indicated pin. - * The constexpr version of the function is identical, but verifies that the pin is valid at compile time. + * This method enables library control of the given output pin and sets the brightness. */ void setBrightness(uint8_t pin, float value); + /** + * @brief Disables and detaches the pin from the library. + * @param pin The pin to relenquish control of. + * This method disables control of the given pin and stops the library from controlling it. + * Note that both pins start disabled; in order to enable them you can call `setBrightness`. + */ + void disable(uint8_t pin); /** * @brief Gets the currently-set brightness. * @param pin The pin controlling the desired channel. Only pins 9 and 10 are supported. * This method retrieves the brightness for the channel controlled by the indicated pin. - * The constexpr version of the function is identical, but verifies that the pin is valid at compile time. */ float getCurrentBrightness(uint8_t pin); @@ -60,6 +68,7 @@ namespace TriacDimmer { * @brief Sets channel A phase angle. * @param value The phase angle, 0.0 fires immediately, 1.0 fires one period later. * This method directly sets the phase angle used to control brightness on channel A (pin 9). + * Note that this method does not enable the pin if it is disabled. */ void setChannelA(float value); @@ -67,6 +76,7 @@ namespace TriacDimmer { * @brief Sets channel B phase angle. * @param value The phase angle, 0.0 fires immediately, 1.0 fires one period later. * This method directly sets the phase angle used to control brightness on channel B (pin 10). + * Note that this method does not enable the pin if it is disabled. */ void setChannelB(float value); @@ -82,11 +92,43 @@ namespace TriacDimmer { */ float getChannelB(); + /** + * @brief Disables channel A. + * This method disables timer control of channel A (pin 9). Set `ch_A_en` to re-enable. + */ + void disableChannelA(); + + /** + * @brief Disables channel B. + * This method disables timer control of channel B (pin 10). Set `ch_A_en` to re-enable. + */ + void disableChannelB(); + /** * @brief Stores the configured pulse length. */ - extern volatile uint16_t pulse_length; + extern uint16_t pulse_length; + + /** + * @brief Stores the configured minimum trigger time. + */ + extern uint16_t min_trigger; + + /** + * @brief Stores the configured minimum trigger time. + */ + extern float on_thresh; + + /** + * @brief Stores the configured minimum trigger time. + */ + extern float off_thresh; + + /** + * @brief Stores whether channel A is enabled. + */ + extern volatile bool ch_A_en; /** * @brief Stores the channel A positive edge delay @@ -98,6 +140,11 @@ namespace TriacDimmer { */ extern volatile uint16_t ch_A_dn; + /** + * @brief Stores whether channel B is enabled. + */ + extern volatile bool ch_B_en; + /** * @brief Stores the channel B positive edge delay */