Custom matrix; dimming individual LEDs

Hello.

I've been fiddling around with this project for a couple of years, never having actually gotten it to work. I am so close now, I can almost taste it.

The idea is to be able to dim any number of LEDs in a 60-lamp matrix. Maybe I'll just dim up and down A7 or maybe I'll want to bring up B4, B5, B6, C3, D8 and F0. It depends upon the arrangement of where the LEDs sit on a prop and the colors of the individual lamps.

I finally decided that I don't have the software chops to do this on a PIC or a vanilla AVR and moved it over to Arduino Duem/328 a few weeks ago. It is just the Arduino, resistors on the rows and a UNL2803 on the columns. I've attached a wiring diagram.

I chose B. Hagman's SoftPWM library (Google Code Archive - Long-term storage for Google Code Project Hosting.) to handle the PWM; I'm not wedded to this library, but it looks pretty good, and I've been able to dim up and down on specific colors (because of the way the LEDs are laid out).

But I don't have a clue how to bring up and down specific lamps perceived to be all at the same time. I want to do patterns, which may mean I'll need to build tables that have the patterns pre-built.

For example, in the code below, I can't make the blue lamp at position A7 (or the first column, eighth lamp) ramp up and down with the other blue lamps.

Here is the code I've cobbled together; I found that if I tried to do the digitalWrites in loops, everything flickered, but by just writing the lines one by one, I got smooth dims:

#include <SoftPWM.h>

byte col = 0;
byte leds[8][8];

// pin[xx] on led matrix connected to nn on Arduino (-1 is dummy to make array start at pos 1)
int pins[17]= {-1, 5, 4, 3, 2, 14, 15, 16, 17, 13, 12, 11, 10, 9, 8, 7, 6};

// col[xx] of leds
int cols[9] = {pins[0], pins[13], pins[3], pins[4], pins[10], pins[6], pins[11], pins[15], pins[16]};

// row[xx] of leds
int rows[9] = {pins[0], pins[9], pins[14], pins[8], pins[12], pins[1], pins[7], pins[2], pins[5]};

void setup()
  {
//Sets the cols pins as output
  for (int i = 1; i <= 8; i++)
    {
      pinMode(cols[i], OUTPUT);
    }

//Set up cols 
  for (int i = 1; i <= 8; i++)
    {
      digitalWrite(cols[i], LOW);
    }
//Initialize SoftPWM
  SoftPWMBegin();
    
// Create and sets row pins
  for (int i = 1; i <= 8; i++)
    {
      SoftPWMSet(rows[i], 0);
    }

}

void loop()
  {
//alt blue2
  for (int b = 1; b <=200; b++)
    {
      SoftPWMSet(rows[5], b);
      SoftPWMSet(rows[6], b);
      SoftPWMSet(rows[7], b);
      digitalWrite(cols[1], HIGH);
      digitalWrite(cols[2], HIGH);
      digitalWrite(cols[3], HIGH);
      digitalWrite(cols[4], HIGH);
      digitalWrite(cols[5], HIGH);
      digitalWrite(cols[6], HIGH);
      digitalWrite(cols[7], HIGH);
      digitalWrite(cols[8], HIGH);
      delay(10);
      digitalWrite(cols[1], LOW);
      digitalWrite(cols[2], LOW);
      digitalWrite(cols[3], LOW);
      digitalWrite(cols[4], LOW);
      digitalWrite(cols[5], LOW);
      digitalWrite(cols[6], LOW);
      digitalWrite(cols[7], LOW);
      digitalWrite(cols[8], LOW);
    }
  for (int b = 200; b >= 1; b--)
    {
      SoftPWMSet(rows[5], b);
      SoftPWMSet(rows[6], b);
      SoftPWMSet(rows[7], b);
      digitalWrite(cols[1], HIGH);
      digitalWrite(cols[2], HIGH);
      digitalWrite(cols[3], HIGH);
      digitalWrite(cols[4], HIGH);
      digitalWrite(cols[5], HIGH);
      digitalWrite(cols[6], HIGH);
      digitalWrite(cols[7], HIGH);
      digitalWrite(cols[8], HIGH);
      delay(10);
      digitalWrite(cols[1], LOW);
      digitalWrite(cols[2], LOW);
      digitalWrite(cols[3], LOW);
      digitalWrite(cols[4], LOW);
      digitalWrite(cols[5], LOW);
      digitalWrite(cols[6], LOW);
      digitalWrite(cols[7], LOW);
      digitalWrite(cols[8], LOW);
    }
      SoftPWMSet(rows[5], 0);
      SoftPWMSet(rows[6], 0);
      SoftPWMSet(rows[7], 0);

//alt green2
  for (int g = 1; g <=200; g++)
    {
      SoftPWMSet(rows[3], g);
      SoftPWMSet(rows[4], g);
      digitalWrite(cols[1], HIGH);
      digitalWrite(cols[2], HIGH);
      digitalWrite(cols[3], HIGH);
      digitalWrite(cols[4], HIGH);
      digitalWrite(cols[5], HIGH);
      digitalWrite(cols[6], HIGH);
      digitalWrite(cols[7], HIGH);
      digitalWrite(cols[8], HIGH);
      delay(10);
      digitalWrite(cols[1], LOW);
      digitalWrite(cols[2], LOW);
      digitalWrite(cols[3], LOW);
      digitalWrite(cols[4], LOW);
      digitalWrite(cols[5], LOW);
      digitalWrite(cols[6], LOW);
      digitalWrite(cols[7], LOW);
      digitalWrite(cols[8], LOW);
    }
  for (int g = 200; g >= 1; g--)
    {
      SoftPWMSet(rows[3], g);
      SoftPWMSet(rows[4], g);
      digitalWrite(cols[1], HIGH);
      digitalWrite(cols[2], HIGH);
      digitalWrite(cols[3], HIGH);
      digitalWrite(cols[4], HIGH);
      digitalWrite(cols[5], HIGH);
      digitalWrite(cols[6], HIGH);
      digitalWrite(cols[7], HIGH);
      digitalWrite(cols[8], HIGH);
      delay(10);
      digitalWrite(cols[1], LOW);
      digitalWrite(cols[2], LOW);
      digitalWrite(cols[3], LOW);
      digitalWrite(cols[4], LOW);
      digitalWrite(cols[5], LOW);
      digitalWrite(cols[6], LOW);
      digitalWrite(cols[7], LOW);
      digitalWrite(cols[8], LOW);
    }
SoftPWMSet(rows[3], 0);
SoftPWMSet(rows[4], 0);


//alt red
  for (int r= 1; r<=200; r++)
    {
      SoftPWMSet(rows[1], r);
      SoftPWMSet(rows[2], r);
      SoftPWMSet(rows[8], r);
      digitalWrite(cols[1], HIGH);
      digitalWrite(cols[2], HIGH);
      digitalWrite(cols[3], HIGH);
      digitalWrite(cols[4], HIGH);
      digitalWrite(cols[5], HIGH);
      digitalWrite(cols[6], HIGH);
      digitalWrite(cols[7], HIGH);
      digitalWrite(cols[8], HIGH);
      delay(10);
      digitalWrite(cols[1], LOW);
      digitalWrite(cols[2], LOW);
      digitalWrite(cols[3], LOW);
      digitalWrite(cols[4], LOW);
      digitalWrite(cols[5], LOW);
      digitalWrite(cols[6], LOW);
      digitalWrite(cols[7], LOW);
      digitalWrite(cols[8], LOW);
    }
  for (int r= 200; r>= 1; r--)
    {
      SoftPWMSet(rows[1], r);
      SoftPWMSet(rows[2], r);
      SoftPWMSet(rows[8], r);
      digitalWrite(cols[1], HIGH);
      digitalWrite(cols[2], HIGH);
      digitalWrite(cols[3], HIGH);
      digitalWrite(cols[4], HIGH);
      digitalWrite(cols[5], HIGH);
      digitalWrite(cols[6], HIGH);
      digitalWrite(cols[7], HIGH);
      digitalWrite(cols[8], HIGH);
      delay(10);
      digitalWrite(cols[1], LOW);
      digitalWrite(cols[2], LOW);
      digitalWrite(cols[3], LOW);
      digitalWrite(cols[4], LOW);
      digitalWrite(cols[5], LOW);
      digitalWrite(cols[6], LOW);
      digitalWrite(cols[7], LOW);
      digitalWrite(cols[8], LOW);
    }

SoftPWMSet(rows[1], 0);
SoftPWMSet(rows[2], 0);
SoftPWMSet(rows[8], 0);



  }

}

I'd be interested in hearing theories on how to do what I want with this library or with any other techniques.

Thanks so much.

\dmc

I think you'd have better success with other chip like TLC5940 (or 5941) (column) with 74595 (row). Controlling your PWM with as it deisgn would be a bit challenge, I’d imagine.

Tokuro:

Thanks for the thought ... as I said, I've been doing this for a couple of years and have a drawer full of chips that I haven't been able to make work, including the 5940 and a variety of 595s.

Additionally, a chip like the 5940 throws away eight pins when configured to drive this type of matrix and while I don't plan on building a million (or even 100), it's an expensive solution to what should be a simple problem: PWM on eight pins and high and low on eight pins.

This hardware works; I'm looking for some software insights to make it do exactly what I want.

Again, thanks.

\dmc

You might want to look at my DLT project. It does exactly what you're looking to do - except the DLT is only 5x5 (easy to expand to 8x8).

In the case of the DLT, I separated the LED control to one Wiring S board, and I'm using a second Wiring S board for controlling the rMP3 and input.

Please mind the mess on the wiki - I don't get much time to work on it, so I add to it when I can. I'll put the current source code for the LED controller so that you can see what's going on.

The trick with the SoftPWM Library with an LED matrix is to use an "undocumented" feature of the SoftPWMSet function which makes SoftPWM begin it's pulses when the SoftPWMSet function is called (I call it "hard set"). It's a "hidden" third parameter to SoftPWMSet, which takes TRUE or FALSE (it defaults to FALSE).

Have a look at the code - it's a bit to wade through, but you should be able to find what you're looking for.

This hardware works; I'm looking for some software insights to make it do exactly what I want.

Only certain pins can produce PWM, you seem to have scattered them between columns and rows with no thought to how you will use them.
If you want to be able to dim individual LEDs in a scanning matrix with PWM you will have to synchronise the matrix scanning with the PWM signal. This you can't do. You will also have to have a PWM output for each row, that's 8 PWM outputs, this you don't have unless you have a mega.

If you want to dim the LEDs by just software you have to have N scans per column scan, where N is the number of brightness levels you want. You do not have time to do this and get the whole refresh rate at 30 per second to prevent flicker.

Mike:

Thanks for your insights.

This project is based on a schematic from another hobbyist. While he has been generous about sharing the electronic design, he has kept the firmware private, only distributing hex code. I have had a series of messages with him over the years and he has no problem with me trying to reverse-engineer the code.

The original project is based on the PIC18f2620, which while having 40 pins, is remarkably similar to the Atmel 328 in terms of horsepower. In fact, the Atmel chip supports more hardware PWM pins than the PIC, so it's clear that the original design used software PWM.

In its current iteration, the code easily fades in and out the three groups of colored LEDs, so there is some method to what you perceive as madness. The wiring of the Arduino pins is taken from this playground entry: Arduino Playground - DirectDriveLEDMatrix ... perhaps I shouldn't have followed an official entry, but it seemed like a good way to at least start.

I feel I am maybe 80 or 90 percent of the way to my goal at this point -- and am just looking for the right trick to finish it off -- but perhaps you are right: maybe this project is doomed to never work.

\dmc

The SoftPWM library provides a way to synchronize the start of the PWM pulses with the column/row (whatever your fancy) scan, as I described above.

dmc: I'm thinking you just need to add the third "TRUE" parameter to each of the SoftPWMSet(). e.g.:

SoftPWMSet(rows[1], r, TRUE);

Mike:

On the DLT, the end result is 60Hz refresh (over 5 columns), with just over 48 shades of brightness. One could, of course, tweak the values and get a wider range of shades, but I stuck with 48 shades. e.g.: @ 30 Hz, you could get 96 shades, and so on.

So, while I am certainly nervous about arguing with Grumpy Mike :P, I have to say that it is possible to achieve what you want to do with software.

Let me know if you have any questions about the DLT sketches/code, or about the SoftPWM library (that goes for you too, GM) :slight_smile:

dmc: I'm thinking you just need to add the third "TRUE" parameter to each of the SoftPWMSet(). e.g.:

SoftPWMSet(rows[1], r, TRUE);

Brett:

Yes, I had fiddled with that yesterday. I found that the parameter TRUE causes the IDE to barf ("'TRUE' was not declared in this scope").

Here's the salient lines (I think) from SoftPWM.cpp:

  if (hardset)
  {
    TCNT2 = 0;
    _isr_softcount = 0xff;
  }

I don't know what those two lines do, but I surmised that the code is expecting the parameter to be an integer, not a word.

So, I tried setting up something like

      SoftPWMSet(rows[8], b, 1);
      digitalWrite(cols[1], HIGH);

which works, but I see no substantial difference than without the parameter.

I'm stumbling over the basic -- undoubtedly elementary -- concept of how to walk through the rows and columns and holding the values for a sufficient time that they effect the LEDs.

Again, I'm sure there's a trick here that I'm missing.

Thanks.

\dmc

Since you said you are not maried to the SoftPWM lib, how about this one just launched:

http://arduino.cc/forum/index.php/topic,66988.0.html

Feel free to slap me with a trout if my newbyness is too apparent.

Gustavo:

I'm not saying it won't work, but it does require shift registers, which the circuit I'm reverse-engineering doesn't have.

I'll keep it in my back pocket if I come up empty with the current direction.

Thanks.

\dmc

This is some code to show you the basic idea of dimming LEDs in a matrix using PWM generated by the refresh routine.
In order to get to 30 complete refreshes per second you have to run the refresh every 0.2mS, this sample code just refreshes it at 1mS intervals.
It is untested, you might have to change the pin assignments to match the pins you have used I I had not access to your circuit when I wrote this.

/*
 * Dimming matrix - Mike Cook
 *
 * multiplexes a matrix and allows each LED to have its brightness controlled
 * with 16 levels of brightness for each
 * all rows on at any one time with the multiplexing happening on the columns
 ****** warning this is not tested code may contain nuts ********
 */

byte rowPins[] = {2, 3, 4, 5, 6, 7, 8, 9 };  // set up the 8 pins to use as row output - source for LEDs through a resistor
byte colPins[] = {10, 11, 12, 13, 14, 15, 16, 17 };  //  set up the 8 pins to use as column output driving a darlington pulling to ground
long int pattern[] = {0x01234567, 0x89ABCDEF, 0x76543210, 0xFEDCBA98, 0x01234567, 0x89ABCDEF, 0x76543210, 0xFEDCBA98};  // array to hold the pattern data
// each LED's brightness is stored in 4 bits in a long int giving data for all 8 LEDs in a column in one long int
long int refreshTime;

byte tick = 0, col = 0;  // global variables to keep track of what to do next

void setup()                    // is run once, when the sketch starts
{
  for(int i=0; i<8; i++){
  pinMode(rowPins[i], OUTPUT);      // sets the digital pins as output
  pinMode(colPins[i], OUTPUT);
  digitalWrite(rowPins[i], LOW);    // initally turn them all off
  digitalWrite(colPins[i], LOW);
  }
  refreshTime = millis();   // to see when to refresh the matrix
}

void loop()                     // run over and over again
{ 
   if(millis() >= refreshTime) {
      refreshTime = millis()+1;   // actually needs to be done every 0.2mS but here doing it every 1mS
      refreshMatrix();
     }

}  // end of loop() function

void refreshMatrix(){
  tick++;
  if((tick & 0x10) !=0) { // tick has overflowed so move on to next column
      tick = 0; // reset tick
      digitalWrite(colPins[col], LOW); // turn off the active coloum
        for(int i = 0; i<8; i++) { // turn off all the rows
          digitalWrite(rowPins[i], LOW);
           }
        col = (col +1 ) & 0xF; // increment column and wrap round if need be to keep value from 0 to 7
        digitalWrite(colPins[col], HIGH); // turn on new coloum
       }
   long int data = pattern[col];
   for(int i = 0; i<8; i++) { // turn on / off the individual LEDs
   if((data & 0xF) > tick) digitalWrite(rowPins[i], LOW); else digitalWrite(rowPins[i], HIGH);  // turn LED on or off that is do the PWM bit
   data = data >> 4; // move the pattern data so that the next LED information is in the bottom four bits
   }
}  // end of refresh matrix

GM:

OK, very interesting.

The refresh rate of +1, as you suggested in the comment, was insufficient (could have induced seizures); I changed first to .2 and then to .1 and things now seem to be OK.

I believe the key insight you provided was

// each LED's brightness is stored in 4 bits in a long int giving data for all 8 LEDs in a column in one long int

So the idea is to envision a specific pattern, calculate the brightness of all the LEDs in a column for a specific iteration of the pattern and then load each iteration as a HEX value into a table?

If I'm right there, how would one go about calculating the HEX values? It appears in the sample code you provided, you just created random HEX values ... (ABCD, 1234, etc.).

This is where my non-engineering background shows (more than most times, anyway), so please be gentle.

Thanks.

\dmc

I changed first to .2 and then to .1 and things now seem to be OK.

Where? the line
refreshTime = millis()+1;
is an integer so adding 0.1 or .2 makes no sense at all, in fact it is adding zero so it just refreshes as fast as it can which is not what you want because the refresh code takes a variable time to execute and you need a fixed rate of refresh to have the brightness stable.

It appears in the sample code you provided, you just created random HEX values

No these are brightness scale, take the first value
0x01234567
this breaks down into
Row 0 LED brightness level 0 (that's off)
Row 1 LED brightness level 1
Row 2 LED brightness level 2
Row 3 LED brightness level 3
Row 4 LED brightness level 4
Row 5 LED brightness level 5
Row 6 LED brightness level 6
Row 7 LED brightness level 7
for column 0
the next long int is for column 1
0x89ABCDEF - this is increasing in brightness
then for column 2
0x76543210 - decreases brightness for each LED.
So not random at all.

If I'm right there, how would one go about calculating the HEX values?

Split your patten into columns and write down the hex value of the required brightness.

So the idea is to envision a specific pattern, calculate the brightness of all the LEDs in a column for a specific iteration of the pattern and then load each iteration as a HEX value into a table?

Yes that is how LED matrix refreshing works.

Grumpy_Mike:

I changed first to .2 and then to .1 and things now seem to be OK.

Where? the line
refreshTime = millis()+1;
is an integer so adding 0.1 or .2 makes no sense at all, in fact it is adding zero so it just refreshes as fast as it can which is not what you want because the refresh code takes a variable time to execute and you need a fixed rate of refresh to have the brightness stable.

I always hate it when I do things that make no sense but then work.

Yes, I changed the code to read

          refreshTime = millis()+.1;   // actually needs to be done every 0.2mS but here doing it every 1mS

And it went from all jumpy to the appearance of being constantly on ... some columns are brighter than others, everything except Column 8 is "on" ... all the LEDs in Column 8 appear to "off" (quotes, because I know we're talking appearances not reality).

I will examine your explanation of the HEX code more carefully before responding.

Thanks.

\dmc

all the LEDs in Column 8 appear to "off"

Check that rowPins[] and colPins[] match the pins you are using and their contents are in the right order for your matrix.

It is probably the extra time it takes trying to add 0.1 that is smoothing things. However why this is diffrent from trying to add 0.2 I don't know. You could try a delay microseconds in place of the mills() test. The LEDs in each column should be of varying brightness as well, but as I said it is a first go at the code and I have not tried it.

Grumpy_Mike:
The LEDs in each column should be of varying brightness as well, but as I said it is a first go at the code and I have not tried it.

So you envisioned this code would gradually fade in and then fade out of all LEDs?

\dmc

No there is no dynamic operation, but each LED in each column should be a diffrent brightness.

Grumpy_Mike:
No there is no dynamic operation, but each LED in each column should be a diffrent brightness.

OK, that's not what I'm getting, but of course I need to double-check everything.

Thanks for your help; you're providing many insights I had gleaned nowhere else.

\dmc

So have you changed the pin assignments?
I looked at your diagram and these need to be:-

byte rowPins[] = {13, 12, 11, 10, 9, 8, 7, 6 };  // set up the 8 pins to use as row output - source for LEDs through a resistor
byte colPins[] = {5, 4, 3, 2, 14, 15, 16, 17 };  //  set up the 8 pins to use as column output driving a darlington pulling to ground

Yes, I had changed the tables to match the schematic.

No, I had not wired the board to match the schematic :frowning: ...

When I made the board and the tables match, voila, many lit LEDs.

Some were not, though. I then changed the pattern table to look like this:

long int pattern[] = {0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777, 0x77777777};  // array to hold the pattern data

And all the LEDs appear lit (well, save one -- somewhere along the line I know I fried it).

So, I guess I still don't understand what your patterns were attempting to achieve (head hard as rocks ... sorry) ...

This whole notion of building a variable table with patterns in it that is uploaded all at once is the "trick" I kept saying I knew must be out there. I'll try building some of my own and post back here as I progress.

Thanks.

\dmc