I'm trying to use an Arduino to dim a "dimmable" LED buib - a GU5.3 3W halogen replacement While advertised as dimmable, it does not react well to the PWM signal. There is some reaction, but only on the low end of the PWM. it doesn't take much to get to full brightness. My circuit is a standard low side MOSFET with PWM input.
Putting the original halogen lights back into the armature gives perfect dimming behaviour, but for power and heat reasons I want to use the LEDs. A piece of 12V LED strip also works beautifully.
As experiment I've ran a 100 Hz PWM signal to the lamp (closer to what it would see using 50 Hz AC and phase cutting), but no difference. 50 Hz PWM made it flicker badly, so that's not an option either.
I found this older forum thread which makes me think those lamps have different internal circuitry...
Any suggestions on what I could try next, short of buying stacks of dimmable LEDs hoping one works?
#include <Encoder.h>
const uint32_t DEBOUNCE_INTERVAL = 50;
const uint32_t OTHER_TIMEOUT = 5000;
//const uint32_t CLICK_TIME = 700;
const uint8_t LAMP_LINKS = 3;
const uint8_t LAMP_RECHTS = 6;
const uint8_t LAMP_ONDER = 9;
const uint8_t LINKS_A = 10;
const uint8_t LINKS_B = 11;
const uint8_t LINKS_S = 12;
const uint8_t RECHTS_A = A1;
const uint8_t RECHTS_B = A2;
const uint8_t RECHTS_S = A3;
//const uint8_t BRIGHTNESS_VALUES = 32;
//const uint8_t logBrightness[BRIGHTNESS_VALUES] = {0, 1, 2, 4, 6, 9, 12, 16,
// 20, 24, 30, 35, 42, 48, 56, 63,
// 71, 80, 89, 99, 109, 120, 131, 143,
// 155, 168, 181, 195, 209, 224, 239, 255
// };
const uint8_t BRIGHTNESS_VALUES = 72; // Encoder makes four steps at a time... This is 3/4 turn for minimum to maximum brightness, 18 steps.
const uint8_t logBrightness[BRIGHTNESS_VALUES] = {0, 0, 0, 1, 1, 2, 2, 3,
4, 5, 6, 7, 8, 9, 11, 12,
14, 16, 18, 20, 21, 24, 26, 28,
30, 33, 35, 38, 41, 44, 47, 50,
54, 56, 60, 63, 67, 70, 75, 79,
82, 87, 91, 95, 99, 104, 108, 113,
117, 123, 128, 133, 139, 143, 149, 154,
160, 165, 171, 178, 183, 190, 195, 202,
207, 215, 220, 228, 233, 241, 249, 255
};
//const uint8_t BRIGHTNESS_VALUES = 96; // Encoder makes four steps at a time... This is 1 turn for minimum to maximum brightness, 24 steps.
//const uint8_t logBrightness[BRIGHTNESS_VALUES] = {0, 0, 0, 0, 1, 1, 1, 2,
// 2, 3, 3, 4, 5, 5, 6, 7,
// 8, 9, 10, 11, 12, 13, 15, 16,
// 17, 19, 20, 21, 23, 24, 26, 28,
// 30, 32, 34, 35, 38, 40, 42, 44,
// 47, 48, 51, 54, 56, 58, 61, 63,
// 66, 69, 71, 75, 77, 80, 84, 86,
// 89, 93, 95, 99, 103, 105, 109, 113,
// 116, 120, 124, 127, 131, 136, 139, 143,
// 148, 151, 155, 160, 163, 168, 173, 176,
// 181, 186, 190, 195, 200, 204, 209, 215,
// 218, 224, 230, 233, 239, 245, 249, 255
// };
//const uint8_t BRIGHTNESS_VALUES = 144; // Encoder makes four steps at a time... This is 1 1/2 turn for minimum to maximum brightness, 36 steps.
//const uint8_t logBrightness[BRIGHTNESS_VALUES] = {0, 0, 0, 0, 1, 1, 1, 1,
// 2, 2, 2, 3, 3, 3, 4, 4,
// 5, 5, 6, 6, 7, 7, 8, 8,
// 9, 10, 11, 11, 12, 13, 13, 14,
// 15, 16, 17, 18, 19, 20, 20, 21,
// 23, 24, 24, 26, 27, 28, 29, 30,
// 32, 32, 34, 35, 37, 38, 39, 41,
// 42, 43, 45, 47, 48, 49, 51, 53,
// 54, 56, 57, 59, 60, 62, 64, 66,
// 67, 69, 71, 74, 75, 77, 79, 81,
// 82, 85, 87, 88, 91, 93, 95, 97,
// 99, 102, 104, 105, 108, 111, 113, 115,
// 117, 120, 121, 124, 127, 130, 131, 134,
// 137, 140, 142, 145, 148, 151, 152, 155,
// 158, 160, 163, 166, 170, 171, 175, 178,
// 181, 183, 186, 190, 193, 195, 199, 202,
// 206, 207, 211, 215, 217, 220, 224, 228,
// 230, 233, 237, 241, 243, 247, 251, 255
// };
Encoder knobLeft(LINKS_A, LINKS_B);
Encoder knobRight(RECHTS_A, RECHTS_B);
enum KnobState {
SELF,
OTHER,
UNDER_SELF,
UNDER_OTHER
};
void setup() {
pinMode(LINKS_A, INPUT_PULLUP);
pinMode(LINKS_B, INPUT_PULLUP);
pinMode(LINKS_S, INPUT_PULLUP);
pinMode(RECHTS_A, INPUT_PULLUP);
pinMode(RECHTS_B, INPUT_PULLUP);
pinMode(RECHTS_S, INPUT_PULLUP);
pinMode(LAMP_LINKS, OUTPUT);
pinMode(LAMP_RECHTS, OUTPUT);
pinMode(LAMP_ONDER, OUTPUT);
}
void loop() {
static KnobState leftKnobState;
static KnobState rightKnobState;
static uint8_t leftBrightness;
static uint8_t rightBrightness;
static uint8_t underBrightness;
static int32_t positionLeft;
static int32_t positionRight;
static uint32_t leftActivity;
static uint32_t rightActivity;
static bool leftPressed;
static bool rightPressed;
static uint32_t lastLeftPressed;
static uint32_t lastRightPressed;
// Read the encoders.
uint8_t newLeft = knobLeft.read();
handleKnob(newLeft, positionLeft, leftKnobState, &leftBrightness, &rightBrightness, &underBrightness, &leftActivity);
positionLeft = newLeft;
uint8_t newRight = knobRight.read();
handleKnob(newRight, positionRight, rightKnobState, &rightBrightness, &leftBrightness, &underBrightness, &rightActivity);
positionRight = newRight;
analogWrite(LAMP_LINKS, logBrightness[leftBrightness]);
analogWrite(LAMP_RECHTS, logBrightness[rightBrightness]);
analogWrite(LAMP_ONDER, logBrightness[underBrightness]);
// Read the encoder switch.
// When switch is closed: set to UNDER.
// When switch is released within 700 ms: switch between SELF and OTHER.
// If OTHER and no activity for 5000 ms: switch to SELF.
handleSwitch(digitalRead(LINKS_S), &leftPressed, &lastLeftPressed, &leftKnobState, leftActivity);
handleSwitch(digitalRead(RECHTS_S), &rightPressed, &lastRightPressed, &rightKnobState, rightActivity);
}
void handleKnob(const int32_t newPosition, const int32_t oldPosition, const KnobState knobState,
uint8_t* selfBrightness, uint8_t* otherBrightness, uint8_t* underBrightness,
uint32_t* activity) {
if (newPosition != oldPosition) {
*activity = millis();
switch (knobState) {
case SELF:
if (newPosition > oldPosition) {
if (*selfBrightness < BRIGHTNESS_VALUES - 1) {
(*selfBrightness)++;
}
}
else {
if (*selfBrightness > 0) {
(*selfBrightness)--;
}
}
break;
case OTHER:
if (newPosition > oldPosition) {
if (*otherBrightness < BRIGHTNESS_VALUES - 1) {
(*otherBrightness)++;
}
}
else {
if (*otherBrightness > 0) {
(*otherBrightness)--;
}
}
break;
case UNDER_SELF:
case UNDER_OTHER:
if (newPosition > oldPosition) {
if (*underBrightness < BRIGHTNESS_VALUES - 1) {
(*underBrightness)++;
}
}
else {
if (*underBrightness > 0) {
(*underBrightness)--;
}
}
break;
}
}
}
void handleSwitch(const bool newState, bool* lastState, uint32_t* lastPressed, KnobState* knobState, const uint32_t activity) {
if (newState == LOW) { // Encoder switch is pressed.
if (*lastState == HIGH) { // But it was not yet pressed.
*lastPressed = millis(); // Record when it got pressed.
*knobState = (*knobState == SELF) ? UNDER_SELF : UNDER_OTHER; // Pressed: now we're managing the UNDER lights.
*lastState = LOW; // Remember the state we're in.
}
}
else { // Switch is now not pressed; newState is HIGH.
if (*lastState == LOW) { // Switch was just released.
if (millis() - *lastPressed > DEBOUNCE_INTERVAL) { // It's been longer than we expect the switch to bounce, so we may act on it.
*lastState = HIGH; // Remember the state we're in.
if (millis() - *lastPressed < millis() - activity) { // No activity since pressed: this is a click to change sides.
*knobState = (*knobState == UNDER_SELF) ? OTHER : SELF;
}
else { // UNDER lights have changed; revert to previous state.
*knobState = (*knobState == UNDER_SELF) ? SELF : OTHER;
}
// if (millis() - *lastPressed < CLICK_TIME) { // Short press: this was a click to switch sides.
// *knobState = (*knobState == UNDER_SELF) ? OTHER : SELF;
// }
// else { // Pressed long - this one was to change the UNDER lights. Revert to previous state.
// *knobState = (*knobState == UNDER_SELF) ? SELF : OTHER;
// }
}
}
}
if (newState == HIGH && *knobState != SELF) { // If set to anything but SELF and the switch is not pressed,
if (millis() - activity > OTHER_TIMEOUT) { // after some time of no activity
*knobState = SELF; // revert to SELF.
}
}
}
