Can I start mirroring the LED color to my own LED?

Yes, The D0 is redLED, D1, is greenLED, D2 is blueLED.

As a test, you could increment a counter each time ledChangeHandler is called and output that to serial in your loop. That will verify that the system is providing the color change notifications. You could even save the r,g,b, values to global values and output that to serial in your loop().

Also, what happens if you remove the LED handler and simply write:

analogWrite(D0, 255);
analogWrite(D1, 255);
analogWrite(D2, 255);

in setup after setting the pinMode? You should see the LED glow white.


it lights all leds up, but works like the LED on board.

I start thinking about editing the code from build folder and other hardware code.

hi , since the new version of the hardware can’t provide correct mirroring function of the on-board RGB LED, I am looking for the way to hijack the on-board LED pints to my LED. Does anyone know how to re-direct the on-board RGB LED pins to D0, D1, D2?

Why do you say that it cannot mirror the on-board RGB led function?

@wgbartley did it…

1 Like

True story!


Thank you @wgbartley,

This is my code. What should I change to make it work?

uint8_t redLED = D0;
uint8_t greenLED = D1;
uint8_t blueLED = D2;

void ledChangeHandler(uint8_t r, uint8_t g, uint8_t b)
	analogWrite(redLED, r);
	analogWrite(greenLED, g);
	analogWrite(blueLED, b);

void setup(void) 
	//Server to Client(Photon) command

	Spark.function("setLive", startLivePublish);


Did you try my suggestion to output the RGB values to Serial?

Also just to make sure it’s nothing with your toolchain, try this code via Particle Build (Web IDE) and make sure to select the correct target “Photon with firmware (Latest 0.4.5)

I’ve just tested your above code (had to comment Spark.function() and added a void loop()) and it just works as expected.
I get a nice clean oszillogram on all three pins that shows me nice breathing colors.

So I’d guess the problem is actually on your side!

Maybe try altering @mdma’s code above to use 127s instead of 255s.

analogWrite(D0, 127);
analogWrite(D1, 127);
analogWrite(D2, 127);

It’s grasping at straws, but maybe the pins or the LEDs don’t like being anything other than 0 or 255?

I know the RGB.onChange(...) stuff work for both Core and Photon when compiling/flashing from CLI. I have modified several past projects to use it just because it exists. Everyone should be able to bask in the glow of breathing cyan whenever and wherever they want!

@wgbartley, I’ve just confirmed that at least two of my Photons do exactly what they need to do - breathing cyan gives me a nice growing/shrinking square wave on D1 and D2 and D0 works just fine too.

Either it’s some electric problem with the OPs LEDs or a local build issue.

Maybe @mikelo could shoot a pic or better video of his test setup and post here.

And try a cloud build rather than a local!

If you got 100+ Photons, try some other ones :wink:

BTW: There are even three solder pads on the bottom side of the Photon which expose the RGB-LED pins for SMD mount of headerless Photons :sunglasses:

Looks like our example in the docs is borked if you are working with a Common Cathode RGB LED. I just tried it and ended up with a White LED. This code works though, just tested with a 0.4.5 P1 & Photon.

Note: I’m using a Common Cathode external RGB LED. Invert the handler logic for Common Anode, i.e. 255-r instead of r. Also #include "application.h" was necessary and not normally needed for .ino apps in Build… not sure what that’s about. setup() and loop() are not required… but I thought I’d add them to make everything super clear.

 *  Copyright (c) 2015 Particle Industries, Inc.  All rights reserved.
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <>.

#include "application.h"

// Automatically mirror the onboard RGB LED to an external RGB LED
// No additional code needed in setup() or loop()

class ExternalRGB {
    ExternalRGB(pin_t r, pin_t g, pin_t b) : pin_r(r), pin_g(g), pin_b(b) {
      pinMode(pin_r, OUTPUT);
      pinMode(pin_g, OUTPUT);
      pinMode(pin_b, OUTPUT);
      RGB.onChange(&ExternalRGB::handler, this);

    void handler(uint8_t r, uint8_t g, uint8_t b) {
      // Mirror system RGB LED to external common cathode RGB LED
	  // Called every 1ms, keep this as short as possible!
      analogWrite(pin_r, r);
      analogWrite(pin_g, g);
      analogWrite(pin_b, b);

      pin_t pin_r;
      pin_t pin_g;
      pin_t pin_b;

// Connect an external RGB LED to D0, D1 and D2 (R, G, and B)
ExternalRGB myRGB(D0, D1, D2);

void setup() {
	// Nothing to setup for ExternalRGB, already done in constructor.

void loop() {


Also I tested your code and it works fine, it just doesn’t update the external LED until your Photon connects to the Cloud and runs setup(). Use my code above to start updating the RGB LED from the time the system boots and shows a White LED.

1 Like

This also works to enable a mirrored RGB that lights up White right when the system boots… even though the STARTUP() macro is taking care of putting code in a constructor for you, it feels wrong to me… but if it feels right to you, use it! :smile:

void ledChangeHandler(uint8_t r, uint8_t g, uint8_t b)
    analogWrite(D0, r);
    analogWrite(D1, g);
    analogWrite(D2, b);

    pinMode(D0, OUTPUT);
    pinMode(D1, OUTPUT);
    pinMode(D2, OUTPUT);

void setup() { 

void loop() { 

@mikelo, any updates on your side?
Still having problems?

This is great. Since my Project IS actually a connected LED Lamp i can mirror the Status LED to the whole lamp indicating if it is connected or not. I was surprised it even works in listening mode which is just perfect for my case - thought i have to wait until the System Code runs in another Thread.

This works fine for me with a Neopixel ring:


void setup() {


pinMode(irqpin, INPUT);
digitalWrite(irqpin, HIGH);


while(!Particle.connected()) {

// Other Stuff

colorWipe(strip.Color(0, 0, 0), 40);


 void ledChangeHandler(uint8_t r, uint8_t g, uint8_t b) {

     if(!Particle.connected()) {
         colorNoWipe(strip.Color(r, g, b));



@mikelo, I hope I’m not too late to regain your trust into Particle.

After some more testing I think I found the actual reason for the confusion here.

As said by most of the contributors in this thread, RGB.onChange() does work as expected, BUT analogWrite() does not.
The underlying problem is a timing problem in combination with some very hardware specific behaviour.

If you call analogWrite() to quickly (> 500Hz) - which is the case with RGB.onChange() when e.g. breathing - the PWM cycle gets cut short resulting in a HIGH retrigger before the pending LOW phase has finished.
This gives you a wrong pulse-pause-ratio which.

A possible workaround till this analogWrite() behaviour gets corrected (GitHub issue #650 opened for it) would look like this


uint32_t ms;

void ledChangeHandler(uint8_t r, uint8_t g, uint8_t b)
    if(millis() - ms > 10)
    { // only update PWM every 10ms or so
      #ifdef COMMON_ANODE
     	analogWrite(D0, 255-r);
    	analogWrite(D1, 255-g);
    	analogWrite(D2, 255-b);
     	analogWrite(D0, r);
    	analogWrite(D1, g);
    	analogWrite(D2, b);
        ms = millis();

void setup(void) 

void loop() { }

@BDub, this is also the reason why you saw the white LED.


@ScruffR that’s a very keen observation and one that can be tested pretty easily with timers. We know the onChange() handler is being called every 1ms, so if it takes analogWrite() a long time to complete… that’s no good. Also if analogWrite() still re-initializes the PWM every time it’s called, that’s needs to be fixed so it’s just updating the CCR register every successive call after first init.

The white’ish LED that I see when the system is running is because the example code was trying to drive a common anode RGB instead of a common cathode. And when you get those mixed up, you are basically overdriving all of the segments so they appear white’ish. I like your #define COMMON_ANODE as a cue to the reader what’s happening there.

1 Like

Be careful with this. I had my giant segment clock (58 NeoPixels) do this and when it goes to rapidly flashing green and then rapid cyan, you have a really big strobe light. Some people may not respond well to this.


Yeah, I can confirm that 1024 RGB LEDs shooting photons at you may be unpleasant :wink:

1 Like