Author Topic: 'Neat' LED Array Code  (Read 4037 times)

0 Members and 1 Guest are viewing this topic.

Offline ludzincTopic starter

  • Supporter
  • ****
  • Posts: 507
  • Country: au
    • My Misadventures In Engineering
'Neat' LED Array Code
« on: September 17, 2015, 11:25:09 am »
HI All

I'm making a trinket for the kids for Halloween, which is basically a PCB in a pumpkin shape with LEDs scattered around it.  It's all riven by a micro and I'd like to programme some modes / effects for the LEDs.

Now I'm a hardware guy who's capable of bit bashing but I'd like to learn how to code this 'elegantly' where the one 'effects' routine can be re-purposed.

This is what I  have  in mind, but please feel free to point me in a different direction in case I'm wrong in my thinking! 

For a simple effect I'd like all the LEDs to ramp from off to full bring and then dim in union, in a second mode I'd like to implement a larson scanner effect where a bright dot cycles aroud the board with a trailing dim dot.  So PWM seems obvious here but how to do it nicely?

I was thinking of an array that holds a value that decrements each loop, and each loop happens each interrupt.  Each array element is represents a led and if greater than zero the led is on.  After a 'loop cycle' is complete the array is reset and we repeat.

That bit I grok, the trick is controlling the LEDs.  As my board is a single layer I've laid out the LEDs in non-sequental order.  LED_1 is on PORTA_0 and LED_2 is on PORTB_3 (for example).

I'd love an array where LedArray[ i ] points to the led I want to control, but LedArray[1]=PORTA_0 doesn't work.

So, happy to hear from anyone on what they think I should do, and let me know about the obvious bit's I'm missing.

Thanks,

Simon

« Last Edit: September 17, 2015, 12:30:59 pm by ludzinc »
 

Offline Chris C

  • Frequent Contributor
  • **
  • Posts: 259
  • Country: us
Re: 'Neat' LED Array Code
« Reply #1 on: September 17, 2015, 01:32:35 pm »
Here's how I handle dimming multiple LEDs.  The following example assumes you want 8-bit PWM, on any number of LEDs on two ports.

First, a little setup:

1) Create an array to hold the PWM value for each LED, LedValues[numberofleds].
2) Create two lookup arrays.  LedToPort[numberofleds], that takes a LED # and converts it to a port # (0=A, 1=B).  And LedToBit[numberofleds], that takes a LED # and converts it to a bit position within the port.  For example, your LED_1 would look up to 0 and 0, and LED_2 would look up to 1 and 3.
3) Create an array PortData[2][8].

Then fill in the LedValues according to the current effect.  When that's done, we do a little preprocessing.

For each LED, do the following:
Port = LedToPort[ledno]
Bit = LedToPort[ledno]
Value = LedValues[ledno]
Copy the most significant bit in Value, to the bit at position Bit in PortData[Port][0].
Copy the next significant bit in Value, to the bit at position Bit in PortData[Port][1].
And so on until you get to the least significant bit in Value, copied to PortData[Port][7].

Why do that?  Because it makes PWM generation very, very easy.  Select a time constant, Time.  And do this:

PORTA = PortData[0][0]; PORTB = PortData[1][0]; Wait Time*128;
PORTA = PortData[0][1]; PORTB = PortData[1][1]; Wait Time*64;
PORTA = PortData[0][2]; PORTB = PortData[1][2]; Wait Time*32;
PORTA = PortData[0][3]; PORTB = PortData[1][3]; Wait Time*16;
PORTA = PortData[0][4]; PORTB = PortData[1][4]; Wait Time*8;
PORTA = PortData[0][5]; PORTB = PortData[1][5]; Wait Time*4;
PORTA = PortData[0][6]; PORTB = PortData[1][6]; Wait Time*2;
PORTA = PortData[0][7]; PORTB = PortData[1][7]; Wait Time*1; // for clarity, but can be merged with the next line; just double the wait
PORTA = PortData[0][7]; PORTB = PortData[1][7]; Wait Time*1;
PORTA = PortData[0][6]; PORTB = PortData[1][6]; Wait Time*2;
PORTA = PortData[0][5]; PORTB = PortData[1][5]; Wait Time*4;
PORTA = PortData[0][4]; PORTB = PortData[1][4]; Wait Time*8;
PORTA = PortData[0][3]; PORTB = PortData[1][3]; Wait Time*16;
PORTA = PortData[0][2]; PORTB = PortData[1][2]; Wait Time*32;
PORTA = PortData[0][1]; PORTB = PortData[1][1]; Wait Time*64;
PORTA = PortData[0][0]; PORTB = PortData[1][0]; Wait Time*128;

Congratulations, you just blasted out a full PWM cycle for every LED simultaneously.  Actually, this isn't PWM exactly, but a derivative called MIBAM (Mirror Image Bit Angle Modulation).  It excels at efficiently dimming large quantities of LEDs.  You can read all about it here:

http://www.picbasic.co.uk/forum/showthread.php?t=10564

There's an interactive Javascript tool to let you generate the MIBAM waveform for a certain value, which helped me initially understand it.
 

Offline janoc

  • Super Contributor
  • ***
  • Posts: 3958
  • Country: de
Re: 'Neat' LED Array Code
« Reply #2 on: September 17, 2015, 10:37:51 pm »
...
Port = LedToPort[ledno]
Bit = LedToPort[ledno]
         ^^^^^^ Shouldn't this be LedToBit[ledno] instead?   :-//

Interesting approach, but if I had to drive this many LEDs, I would probably look for a dedicated LED driver with serial interface already. It would simplify things quite a bit. E.g. something like PCA9685, costs around 2 euro.

J.
« Last Edit: September 17, 2015, 10:42:27 pm by janoc »
 

Offline ludzincTopic starter

  • Supporter
  • ****
  • Posts: 507
  • Country: au
    • My Misadventures In Engineering
Re: 'Neat' LED Array Code
« Reply #3 on: September 17, 2015, 10:57:23 pm »
Here's how I handle dimming multiple LEDs.  The following example assumes you want 8-bit PWM, on any number of LEDs on two ports.

First, a little setup:

1) Create an array to hold the PWM value for each LED, LedValues[numberofleds].
2) Create two lookup arrays.  LedToPort[numberofleds], that takes a LED # and converts it to a port # (0=A, 1=B).  And LedToBit[numberofleds], that takes a LED # and converts it to a bit position within the port.  For example, your LED_1 would look up to 0 and 0, and LED_2 would look up to 1 and 3.
3) Create an array PortData[2][8].

Then fill in the LedValues according to the current effect.  When that's done, we do a little preprocessing.

For each LED, do the following:
Port = LedToPort[ledno]
Bit = LedToPort[ledno]
Value = LedValues[ledno]
Copy the most significant bit in Value, to the bit at position Bit in PortData[Port][0].
Copy the next significant bit in Value, to the bit at position Bit in PortData[Port][1].
And so on until you get to the least significant bit in Value, copied to PortData[Port][7].

Why do that?  Because it makes PWM generation very, very easy.  Select a time constant, Time.  And do this:

PORTA = PortData[0][0]; PORTB = PortData[1][0]; Wait Time*128;
PORTA = PortData[0][1]; PORTB = PortData[1][1]; Wait Time*64;
PORTA = PortData[0][2]; PORTB = PortData[1][2]; Wait Time*32;
PORTA = PortData[0][3]; PORTB = PortData[1][3]; Wait Time*16;
PORTA = PortData[0][4]; PORTB = PortData[1][4]; Wait Time*8;
PORTA = PortData[0][5]; PORTB = PortData[1][5]; Wait Time*4;
PORTA = PortData[0][6]; PORTB = PortData[1][6]; Wait Time*2;
PORTA = PortData[0][7]; PORTB = PortData[1][7]; Wait Time*1; // for clarity, but can be merged with the next line; just double the wait
PORTA = PortData[0][7]; PORTB = PortData[1][7]; Wait Time*1;
PORTA = PortData[0][6]; PORTB = PortData[1][6]; Wait Time*2;
PORTA = PortData[0][5]; PORTB = PortData[1][5]; Wait Time*4;
PORTA = PortData[0][4]; PORTB = PortData[1][4]; Wait Time*8;
PORTA = PortData[0][3]; PORTB = PortData[1][3]; Wait Time*16;
PORTA = PortData[0][2]; PORTB = PortData[1][2]; Wait Time*32;
PORTA = PortData[0][1]; PORTB = PortData[1][1]; Wait Time*64;
PORTA = PortData[0][0]; PORTB = PortData[1][0]; Wait Time*128;

Congratulations, you just blasted out a full PWM cycle for every LED simultaneously.  Actually, this isn't PWM exactly, but a derivative called MIBAM (Mirror Image Bit Angle Modulation).  It excels at efficiently dimming large quantities of LEDs.  You can read all about it here:

http://www.picbasic.co.uk/forum/showthread.php?t=10564

There's an interactive Javascript tool to let you generate the MIBAM waveform for a certain value, which helped me initially understand it.

Hey Chris

Thanks for the tips - I'll have  a crack and let you know how I get on!

I'm going to attempt something of the form

PORTA = PortData[ i ][j]; PORTB = PortData[ i ][j]; Wait Time*2^k;

K will have to decrement, hit 1 then increment, but that's manageable.

Thanks!

Simon
« Last Edit: September 17, 2015, 11:20:37 pm by ludzinc »
 

Offline Chris C

  • Frequent Contributor
  • **
  • Posts: 259
  • Country: us
Re: 'Neat' LED Array Code
« Reply #4 on: September 17, 2015, 11:18:10 pm »
^^^^^^ Shouldn't this be LedToBit[ledno] instead?   :-//

You are absolutely right, thank you.  I am leaving my error unedited, to my shame, so that your reply continues to make sense. ;)

I also deliberately omitted a few details for simplicity's sake.  Not sure if [ludzinc] is at the level where comfortable with timers and interrupts yet, but my real MIBAM code runs in a timer interrupt.  Where instead of unrolled full MIBAM cycle generation with waits as I showed, it just sets the ports once, increments an array index to the next operation, sets the next interrupt time, then quickly exits.  And I use a single bitmask value for each port, to ensure it changes only the port bits for those pins actually being used for MIBAM.

Interesting approach, but if I had to drive this many LEDs, I would probably look for a dedicated LED driver with serial interface already. It would simplify things quite a bit.

That works too.  It's the classic time vs. money engineering trade-off.  MIBAM costs more in time to initially understand and implement.  But once done, you can port it to different projects with minimal changes, eliminating the need for an extra IC in each of them.  If you're driving so many LEDs that it exceeds the MCU's max supply current you'll still need extra ICs, but they can be cheaper and more common ones like buffers or shift registers.  Especially nice if you're in a country where getting hold of unusual ICs is expensive, which unfortunately seems to be more common than I thought prior to joining this forum.

MIBAM doesn't seem to be a well known technique, but it is useful, and definitely qualifies as 'neat'.  I figured it could use some exposure, and [ludzinc] would grok even if he decides to use a different approach.
 

Offline Chris C

  • Frequent Contributor
  • **
  • Posts: 259
  • Country: us
Re: 'Neat' LED Array Code
« Reply #5 on: September 17, 2015, 11:26:14 pm »
I'm going to attempt something of the form

PORTA = PortData[j]; PORTB = PortData[j]; Wait Time*2^k;

K will have to decrement, hit 1 then increment, but that's manageable.

Excellent, that you inferred that a real implementation works that way shows you grok. ;)

I'll continue to check this thread to watch for issues or progress.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->