RGB led: can I put only 1 resistor at ground?

Multiplexed PWM for driving a RGB led? Challenge accepted!

Took about 15 minutes to write :slight_smile:

// Multiplexed PWM RGB Led Demo
// By Petri Hakkinen
// 24th November 2014
//
// Arduino driving a single RGB led with only one resistor.
//
// Make the following connections:
// * Connect Arduino pins D2,D3,D3 to anodes of a RGB led.
// * Connect cathode of the RGB led to 330 ohm resistor.
// * Connect the other end of the resistor to ground.
//
// A multiplexed PWM signal is generated by a timer interrupt routine.
// 
// Uses TimerOne library, download from here:
// http://playground.arduino.cc/Code/Timer1

#include <TimerOne.h>

volatile uint8_t color[3];		// R,G,B led intensities in range [0,255]
volatile uint8_t phase = 0;		// phase of the PWM signal, counts from 0 to 255
volatile uint8_t led = 0;		// which led to update, counts from 0 to 2

// Look up table of sin values for mega demo effect.
uint8_t sintab[64];

// The interrupt routine that updates the states of leds.
void interruptRoutine()
{
	// turn all leds off
	PORTD &= B11100011;

	// sample pwm and turn on one led
	if(phase < color[led])
		PORTD |= 1<<(led+2);

	led = (led + 1) & 3;
	phase++;
}

void setup()
{
	pinMode(2, OUTPUT);
	pinMode(3, OUTPUT);
	pinMode(4, OUTPUT);

	// attach interrupt routine
	Timer1.initialize(50);
	Timer1.attachInterrupt(interruptRoutine);

	// init sin table for mega demo effect
	for(uint8_t i = 0; i < 64; i++)
		sintab[i] = (uint8_t)(sin(i * M_PI * 2 / 64) * 127.0f + 128.0f);
}

void loop()
{
	const uint8_t speed = 13;

	color[0] = 0;
	color[1] = 0;
	color[2] = 0;

	// fade red
	for(int i = 0; i < 256; i++) {
		color[0] = i;
		delay(speed);
	}

	// fade green
	for(int i = 0; i < 256; i++) {
		color[1] = i;
		delay(speed);
	}

	// fade blue
	for(int i = 0; i < 256; i++) {
		color[2] = i;
		delay(speed);
	}

	// mega demo effect
	for(int i = 0; i < 500; i++) {
		color[0] = sintab[i & 63];
		color[1] = sintab[(i*2 + 123) & 63];
		color[2] = sintab[(i*2/3 + 35) & 63];
		delay(speed);
	}
}

Cool, something new to look at, :-). A couple of quick comments while mulling things over.

  1. did you also include the time it took to think about what you needed to do [ie, "design
    time"], plus to do background research [eg, to find Timer references], in your 15-minutes?

  2. one problem - you can't have 15-msec delays in your main loop, that'll kill the efficiency
    of your overall program.

The whole point of the exercise is to get multiplexing to work in the context of a "real" program that does something productive, and not just a "demo", as mentioned previously. But, new inputs are always good!

Thanks!

  1. I didn't take time, but I estimate about 15 minutes planning, 15 minutes implementation.

  2. The main loop is just a demo showing that generating the PWM signals don't block the CPU and that the PWM update frequency is independent of the main program. I.e. adding new stuff to the main program does not make the LEDs flicker, a concern that was raised earlier in this thread.

btw. how would you define a "real program"? I think this technique is pretty general and can be used by any program that can yield one timer interrupt for the LED update routine.

To further elaborate: the point of the sketch I did is that you can rip out the entire main loop and replace it with the "real program". The main program can change the values in the color array at will. The interrupt routine is the real beef and takes care of generating the multiplexed PWM signals based on the R,G,B values stored in the array.

You can adjust the update frequency by adjusting the timer period here:

Timer1.initialize(50);

Larger values use less CPU time.

ok, will take me a bit of time to figure out the code.

A real program would be something more than a simple demo, but where the multiplexing scheme might be inserted for monitoring ongoing processes. Eg, something that uses libraries, which tend to be cycle hogs. An example might be reading multiple A/D channels, buffering and formatting the data using sprintf() statements into a nice ASCII string format, and then writing to an SD card.

oric_dan:
ok, will take me a bit of time to figure out the code.

Ok, I will happily answer any questions.

oric_dan:
A real program would be something more than a simple demo, but where the multiplexing scheme might be inserted for monitoring ongoing processes. Eg, something that uses libraries, which tend to be cycle hogs. An example might be reading multiple A/D channels, buffering and formatting the data using sprintf() statements into a nice ASCII string format, and then writing to an SD card.

Sounds doable. I'll leave the details for you to fill out :slight_smile:

Here's the code with "demo parts" ripped out if you want to try plugging in the stuff you mentioned above.

#include <TimerOne.h>

volatile uint8_t color[3]; // R,G,B led intensities in range [0,255]
volatile uint8_t phase = 0; // phase of the PWM signal, counts from 0 to 255
volatile uint8_t led = 0; // which led to update, counts from 0 to 2

// The interrupt routine that updates the states of leds.
void interruptRoutine()
{
 // turn all leds off
 PORTD &= B11100011;

 // sample pwm and turn on one led
 if(phase < color[led])
 PORTD |= 1<<(led+2);

 led = (led + 1) & 3;
 phase++;
}

void setup()
{
 pinMode(2, OUTPUT);
 pinMode(3, OUTPUT);
 pinMode(4, OUTPUT);

 // attach interrupt routine
 Timer1.initialize(50);   // PWM update period here, make it larger if your program needs more CPU time
 Timer1.attachInterrupt(interruptRoutine);
}

void loop()
{
  // <insert your code here, write the intensities of LEDs to color array >
}

I'm not familiar with the TimerOne library. To get this straight,

  1. you didn't actually write a multiplexing routine [from scratch] in 15-minutes, you found someone else's library that generates s.w. PWM using timer interrupts? LOL, good one.

  2. initialize(50) means it's actually generating a timer interrupt every 50-usec, or 20,000 times per sec?

I imagine that's not overly loading down the AVR chip, but it's still a lot of interrupts. Luckily, your interrupt routine code is fairly short. I think one might need to do a fair amount of testing to determine whether all those interrupts might impact other processing in a "real" program. OTOH, you probably don't really need 8-bit PWM for Led intensities, so the interrupts could probably be slowed down by quite a bit. Does this sound right? I'm just trying to analyze what's going on here.

  1. I feel you are trying to downplay my contribution :slight_smile: TimerOne library is only used to attach the interrupt routine. I could have setup the interrupt myself, but why reimplement the wheel? The details of the multiplexed PWM generation is in the interrupt routine I posted. I.e. TimerOne library is not used to generate the PWM signal, because it can not generate a multiplexed PWM signal.

  2. I already mentioned that you can change the timer interrupt frequency. 50 is just a value I threw in. You can use a higher value to slow down the update frequency if it matters in your application.

The number of steps needed depends on the application. It's easy to change. For example, change "phase++" to "phase = (phase + 1) & 63" and use color intensities in range 0-63 for 6 bits.

I suggest you wire this up, shouldn't take more than a few minutes and play around with the code.

Sorry, like I said, I am unfamiliar with TimerOne, so just trying to figure out what is going on here. You said to ask questions [:-)]. Ok, now I see you're doing all the pwm'ing inside the interrupt routine, but I think you need the very high interrupt rate to get good 8-bit PWM.

For example, change "phase++" to "phase = (phase + 1) & 63" and use color intensities in range 0-63 for 6 bits.

I think you could use phase+=4; and initialize(200) to get the same effect, although it may still be adequate [ie, no flickering] at even slower interrupt rates.

And to answer your question: yes, if you lower the number of PWM bits, lets say from 8 bits to 4 bits, so that phase counts from 0-15 instead of 0-255, the PWM loops x16 times faster. In this case you could increase the interrupt period by a factor of 16 without affecting the update frequency.

Also I believe that you can increase 50 in any case (even with 8 bits), I just didn't test with other values.

oric_dan:
I think you could use phase+=4; and initialize(200) to get the same effect, although it may still be adequate [ie, no flickering] at even slower interrupt rates.

Yes, that would work too, same effect.

Tested with "phase += 2" (7 bits resolution) and "Timer.initialize(175)" and seems to work well.

Less than 6 bits is probably not sufficient for slow smooth fades (depends what you want really).

Also if we are talking about optimizations, I would try to squeeze every cycle out of the interrupt routine. Writing it in assembly and getting rid of TimerOne library and its interrupt call overhead would help.

All in all, pretty darn good solution.

oric_dan:
All in all, pretty darn good solution.

Thank you!

I have to give you credit for criticizing the CPU usage. I admit the high interrupt rate can be a problem for CPU heavy programs.

Well, the idea is to try and look at these things from every which angle, isn't it. That's why this thread didn't die after 2 replys, :-). These are good problems to solve.

Also, I just thought to compute the actual update rate. With initialize(50) --> 20,000 interrupts/sec, for the original 8-bit PWM, that's 20000/256= 78 Hz. Divided by 3 Leds gives 26 Hz updates, which would probably result in quite perceivable flicker. OTOH, dropping to 5- or 6-bit PWM and using initialize(100) rather than initialize(200) would lessen the interrupt overhead and should still keep the PWM frequency up at 50 Hz or more, and which would be fine.

oric_dan:
. The other guys who were also pushing its merits just disappeared in a puff of smoke.

I'm still here reading what you are writing.
Thanks Petri

Pelle

I didn't notice any flickering. Note that even at 25-50hz the led is not flickering completely on/off, it's changing color at that frequency r->g->b->r... Etc. Brain is not so sensitive to hue flickering.

Unless of course the color is 255,0,0 or something similar.

Ok, I'm off to bed now, past midnight here. Later!

Flickering doesn't mean completely on-off, it means perceiving a noticeable change in intensity. It may actually only be a few percent to be noticeable. This is why the AC mains use 50-60 Hz. Below that people perceive flickering, and it's very distracting.

This shows that a 1% voltage fluctuation is generally perceivable. It's not a hard threshold, much more complex than that.
http://zone.ni.com/reference/en-XX/help/373375D-01/lveptconcepts/ep_light_flickers/

Ok, tomorrow you can fix your code. Hint: what's the maximum duty cycle each of your Leds can have in the existing code? [I may be wrong, but think it's 33%]. Hint: the "fix" is duplicating line 2 of your ISR twice more.

Had another idea for an RGB heartbeat indicator.

● Demultiplexing millis() for R,G,B using digitalWrite.
● The single resistor is returned through one PWM pin to set a stable intensity.
● As iteration speed slows, intensity remains constant eliminating flicker.
● Also, as iteration speed slows, perceived color varies, then jumping from color to color becomes apparent.
● Very low cpu cycles required, especially if direct port manipulation is used.
● No libraries or interrupts required.

int ledG = 8;   // LED Gnd
int pinR = 9;   // LED R
int pinG = 10;  // LED G
int pinB = 11;  // LED B

const byte intensity = 120; // 0 = off, 255 = full intensity

void setup() {
  pinMode(pinR, OUTPUT);
  pinMode(pinG, OUTPUT);
  pinMode(pinB, OUTPUT);
  analogWrite(ledG, (255 - intensity)); //PWM
}

void loop() {
  byte x = (millis() / 8) % 3; //125ms demultiplexer
  digitalWrite(pinR, 1 * ((x == 0) ? 1 : 0));
  digitalWrite(pinG, 1 * ((x == 1) ? 1 : 0));
  digitalWrite(pinB, 1 * ((x == 2) ? 1 : 0));
}