Sunday, February 22, 2009

Arduino and M5451 -- Control 35 LEDs, motors, etc!

I've been fooling around with an Ardunio, which is a small embedded microprocessor and discovered that its a great way for software engineers to learn some hardware engineering. It is fairly limited in its outputs though, so I bought a couple of M5451 chips (futureelectronics.com MM5451YN for small quantities). This is nice because it is a 40 pin DIP device that plugs right into a breadboard, and is intended to drive LED displays.

But it does not HAVE to be used for LED displays -- I'm thinking of it as 35 constant-current switches that can be conveniently controlled (on/off) by 2 Arduino digital pins (clock and serial data). And if you want to change the current (of all pins) you can use a third pin to connect a "brightness" pin on the 5451 to a PWM on the Arduino.

So this is a *LOT* cooler than the chips that drive 8x8 arrays of LEDs by strobing them (MAX7219, search for "Arduino 8x8 matrix") because those chips flash the LEDs. So they really can't be used for other applications (like driving motors, or turning on a big-current transistor switch), and the LEDs are in theory not as bright because they are not on the entire time. In theory, I should be able to drive 2 of these 8x8 matrixes with one M5451 with 3 outputs left over, or one 16x16 (256 LEDS!) display (if I can get my hands on one).

Also, the cool thing about the M5451 being a constant current driver is that you don't need a current-limiting resistor inline with each LED. And you can connect several LEDs in series without worrying about V=IR math.

But let me emphasize for the beginning hardware DIYer, its important to NOT think of the outputs as logic, just think of them as switches. Basically you connect the M5451 to ground, and connect your load (let's just use LEDs, for example) between the + and it. So the M5451 must be on the cathode, minus, ground side -- whatever you call it -- of your LED. This is IMPORTANT, since if you buy something like RGB LEDs you want to get common ANODE. Remember, relative to the LED, the anode side is where conventional current (+) goes IN.

Of course, I bought the common cathode RGB LEDs (*sigh*).

So here's a picture of a M5451 on a breadboard with a boarduino and with a bunch of LEDs:


Ignore the upper part of the breadboard. all the activity is on the bottom. Also ignore the square silver chip and transistors on the far right. That's just stuff hanging around from another project!

The boarduino is on the bottom left. The 2 pins used are the green and yellow wires on the top side (the blue and short green wires are just power and ground).

You can see all the LEDs bridging from the + rail to the pins on the M5451 (middle)


I wrote a bit of code to drive the M5451 (see below). You can turn on each M5451 switch independently, control the brightness via one of the Arduino's PWM ports (PWM means pulse-width-modulation -- the Arduino toggles the line between 5 and 0 volts rapidly and so that it seems like the line actually has a voltage which is the weighted average of the time the line is 5 vs. 0 -- i.e. a simulated analog voltage).

On top of that, I made a class that is similar to PWM; it toggles the M5451 lines rapidly so the LEDs seem to change brightness.

Here is a video. To make it come out, I have a halogen desk lamp shining directly on the board from about a foot away. About halfway through I turned off the light so you could see the LED brightness in normal room light! Check out the video here:

video


Here's the code:


/*
* M5451 LED driver chip
* Author: G. Andrew Stone
* Public Domain
*/

int myClockPin = 2; // Arduino pin that goes to M5451 clock
int mySerDataPin = 3; // Arduino pin that goes to M5451 data

void setup() // run once, when the sketch starts
{
}


#define M5451_NUMOUTS 35
#define M5451_CLK 0
class M5451
{
public:
byte clockPin;
byte brightPin;
byte serDataPin;
M5451(byte clockPin,byte serDataPin,byte brightPin);

void set(unsigned long int a, byte b=0);
void setBrightness(byte b);

private:
void mydelay(int clk);
};

void M5451::setBrightness(byte b)
{
if (brightPin < 0xff)
analogWrite(brightPin,b);
}

#define MaxBrightness 4096 //256
class FlickerBrightness:public M5451
{
public:
FlickerBrightness(byte clkPin, byte dataPin, byte brightnessPin);

void shift(int amt=1)
{
offset+=amt;
if (offset>=M5451_NUMOUTS) offset -=M5451_NUMOUTS;
else if (offset< 0) offset +=M5451_NUMOUTS;
}
void loop(void);

int brightness[M5451_NUMOUTS];
int bresenham[M5451_NUMOUTS];
int iteration;
int offset;
};

FlickerBrightness::FlickerBrightness(byte clkPin, byte dataPin,byte brightnessPin):M5451(clkPin,dataPin,brightnessPin)
{
for (int i=0;i < M5451_NUMOUTS;i++)
{
brightness[i] = 0;
bresenham[i] = 0;
}

iteration = 0;
offset = 0;
}

void FlickerBrightness::loop(void)
{
int i;
byte pos;
unsigned long int a=0;
byte b=0;
boolean lvl=false;

for (i=0,pos=offset;i < M5451_NUMOUTS;i++,pos++)
{
if (pos>=M5451_NUMOUTS) pos=0;
bresenham[i] += brightness[pos];
if (bresenham[i]>=MaxBrightness)
{
bresenham[i] -= MaxBrightness;
lvl = true;
}
else lvl = false;

if (i<32) a = (a<<1)|lvl;
else b = (b<<1)|lvl;
}
iteration++;
if (iteration > MaxBrightness) iteration = 0;

set(a,b);
}


M5451::M5451(byte clkPin, byte dataPin, byte brightnessPin)
{
int i;

clockPin = clkPin;
serDataPin = dataPin;
brightPin = brightnessPin;

pinMode(clkPin, OUTPUT); // sets the digital pin as output
pinMode(serDataPin, OUTPUT); // sets the digital pin as output
pinMode(brightPin,OUTPUT);

// Clear out the device so we can clock in items
digitalWrite(serDataPin,LOW);
for (i=0;i< M5451_NUMOUTS+2;i++)
{
mydelay(M5451_CLK);
digitalWrite(clockPin,HIGH);
mydelay(M5451_CLK);
digitalWrite(clockPin,LOW);
}
}

void M5451::mydelay(int clk)
{
int i;
for (i=0;i< clk;i++);
//delay(clk);
}

void M5451::set(unsigned long int a, byte b)
{
int i;

// Write the initial "start" signal
digitalWrite(clockPin,LOW);
digitalWrite(serDataPin,LOW);
mydelay(M5451_CLK);
digitalWrite(clockPin,HIGH);
mydelay(M5451_CLK);
digitalWrite(clockPin,LOW);
mydelay(M5451_CLK/2);
digitalWrite(serDataPin,HIGH);
mydelay(M5451_CLK/2);
digitalWrite(clockPin,HIGH);
mydelay(M5451_CLK);
digitalWrite(clockPin,LOW);

// Write the bits
for (i=0;i< M5451_NUMOUTS;i++)
{
int serDataVal;
if (i<32) serdataval =" (a&1);">>=1;}
else { serDataVal = (b&1); b>>=1;}
mydelay(M5451_CLK/2);
digitalWrite(serDataPin,serDataVal);
mydelay(M5451_CLK/2);
digitalWrite(clockPin,HIGH);
mydelay(M5451_CLK);
digitalWrite(clockPin,LOW);
}
}

void loop() // run over and over again
{
unsigned long int j;
int i;
FlickerBrightness leds(myClockPin,mySerDataPin,9);

leds.setBrightness(255);

for (i=3;i>=0;i--)
{
for (j=0;j<35;j++)
{
leds.set(1L<< j,(j>=32) ? 1L<<(j-32):0);
delay(10*i);
}
}

// Proportional fading
if (1) for (j=0;j<200;j++)
{
for (i=0;i< M5451_NUMOUTS;i++)
{
int k = 1<<(j%13);
if ((i&3)<2)
{
if (leds.brightness[i] < 35) leds.brightness[i] = 35;
else leds.brightness[i] += leds.brightness[i]>>2;
}
else
{
if (leds.brightness[i] < 35) leds.brightness[i] = MaxBrightness;
else leds.brightness[i] -= leds.brightness[i]>>2;
}
}
for (i=0;i<100;i++) leds.loop();
}

leds.set(0xffffffff,0xff); /* ALL ON */
delay(250);
leds.set(0,0); /* ALL OFF */
delay(250);

// Linear per-LED brightness method
if (1) for (j=0;j<4096;j++)
{
for (i=0;i< M5451_NUMOUTS;i++)
{
int k = j*10;
if (i&1)
{
leds.brightness[i] = abs((k&(MaxBrightness*2-1))-MaxBrightness);
}
else
leds.brightness[i] = MaxBrightness - abs((k&(MaxBrightness*2-1))-MaxBrightness);
}
for (i=0;i<10;i++) leds.loop();
}


// ALL FADE using M5451 Brightness feature
leds.set(0xffffffff,0xff); /* ALL ON */
for (j=1;j<5;j++)
{
for (i=0;i<256;i++)
{
leds.setBrightness(i&255);
delay(j);
}
for (i=255;i>=0;i--)
{
leds.setBrightness(i&255);
delay(j);
}
}

leds.setBrightness(255);

leds.set(0xffffffff,0xff); /* ALL ON */
delay(250);
leds.set(0,0); /* ALL OFF */
delay(250);


// MARQUEE

for (i=0;i< M5451_NUMOUTS;i++) // Clear all LEDs to black
{
leds.brightness[i]=0;
}

// Turn on a couple to make a "comet" with dimming tail
leds.brightness[0] = MaxBrightness-1;
leds.brightness[1] = MaxBrightness/2;
leds.brightness[2] = MaxBrightness/4;
leds.brightness[3] = MaxBrightness/8;
leds.brightness[4] = MaxBrightness/16;
leds.brightness[5] = MaxBrightness/64;
leds.brightness[6] = MaxBrightness/100;

for (j=0;j<100;j++)
{
leds.shift(1);
for (i=0;i<150;i++) leds.loop();
}

for (j=0;j<100;j++)
{
leds.shift(-1);
for (i=0;i<100;i++) leds.loop();
}


if (1)
{
leds.set(0xffffffff,0x7);
delay(1000);
//leds.set(0xf0f0f0f0,0x7);
//delay(10000);
//leds.set(0x11111111,0x1);
//delay(10000);
}

}

25 comments:

  1. Thanks for sharing! I'm a web designer/developer with 0 electronics experience, so I won't even ask questions. However this blog post inspires future projects after I purchase an Arduino to play with. :)

    ReplyDelete
  2. I am looking forward to using this for my project however I get this error when pasting the code into a new sketch.

    In member function 'void M5451::set(long unsigned int, byte)':
    error: 'serdataval' was not declared in this scope At global scope

    Any ideas?

    ReplyDelete
  3. The blog is mauling the code! The correct line is:

    if (i<32) { serDataVal = (a&1); a >>=1;}

    ReplyDelete
  4. Awesome that was it! You the man! Thanks

    ReplyDelete
  5. I have recreated the circuit minus most of the LED's (i'm using 2 for now). However I cannot get the LED's to light up. I have a capacitor between pin 19 and ground, getting 5v to pin 20, yet nothing. Can I email you a picture of my breadboard?

    ReplyDelete
  6. Sure, send it to g dot andrew dot stone at gmail dot com.

    But first connect a 1K ohm RESISTOR between 5V and pin 19. Pin 19 is brightness control -- the current going through the LEDs will be proportional to the current going into pin 19. The cap on 19 to ground is optional on a breadboard, its just there to stop ringing.

    Also you didn't mention:
    pin 1 should be connected to ground
    pins 21 (lower right) and 22 are clock and data
    if you have the 5450 pin 23 is NOT output enable so should be LOW (on 5451 its another LED)

    finally the LEDs "plus" side should be connected to 5v and the - to each pin on the 5451. The spec is a little unclear in my mind because it calls each LED pin an "output" pin. But really conventional current must flow IN to them.

    ReplyDelete
  7. Thanks for the detailed reply, I finally got it working now! I didnt have the resistor in there. Works great, now I just have to figure out how to get 120 LED's multiplexed :)

    ReplyDelete
  8. Could you post sample code to turn on individual LED's? For example, if I wanted to turn on LED connected to pin 30, 36, and 40, how would I code that?

    ReplyDelete
  9. Ryan, if you want to hack the HW yourself then go for it! But if you just want to get the job done I'll sell you a couple of the boards I made (I have a few extras). They each have 2 M5451s and 2 can be stacked to get you to 140 LEDs. Check later postings in my blog and send me an email if interested.

    Anonymous, the "set" function takes a number whose binary representation corresponds to which LEDs are on (1=on, 0=off). For example, "1" turns on the first LED, "2" the second, and "3" both the first and the second. Not by pin number, but by "output" number (see the M5451 spec)
    So to turn on pins 30,36,and 40. They correspond to outputs 28,22 and 18 respectively. So do:
    leds.set((1<<18)|(1<<22)|(1<<28),0).

    ReplyDelete
  10. Actually that was me who asked the question about how to turn them on. I seem to be able to turn number 1 thru 8 on, however any after that does not work. Is that because the codes needs to be in octal?
    Can you also explain what the last number is for (the "0")?

    Thanks for the help I am getting closer!

    ReplyDelete
  11. Send me your code. The M5451 drives 35 LEDs. Each LED corresponds to one bit in the numbers passed to the set() routine. But a long integer only has 32 bits, so the last number (the 0) is how you control the last 3 LEDs.

    ReplyDelete
  12. Hi, is there any chance that You do wiki article or tutor, how to steer M5451 with arduino? I try to understand this code, but I'm to green.

    ReplyDelete
  13. I have posted code and am working on a wiki at the site:

    http://code.google.com/p/arduino-m5451-current-driver/

    To help you understand it, I need to know where you are... do you have an M5451 hooked up... are you blinking ANY LEDS? Either send me an email (see earlier posts) or post another message.

    ReplyDelete
  14. hey! Great Work!! I was wondering If you still have any of these complete boards for sale.
    If so, could I please get a price and postage to australia.. thankyou very much for your time..

    ReplyDelete
  15. yes. Please send me a message at g.andrew.stone at gmail dot com and we will discuss!

    ReplyDelete
  16. Hi. Thank you very much for the tutorial. It encouraged me to buy a couple of these chips.
    I'm very new to hardware, so forgive me for this question if it should be obvious.
    I just want to be sure about pin 19 (brightness).
    p19 connects to 1K resistor connects to 5v
    AND
    p19 connects to capacitor connects to ground
    AND
    p19 connects to a pwm pin on the Arduino.
    Right?

    And both pins 1 and 20 connect to ground.

    Thanks for the help, and again, thanks for the Wiki and this page.
    Cheers

    ReplyDelete
  17. Close! Connect the resistor to 5v OR a PWM pin on the arduino. Don't connect p19 to the PWM pin direct or there will be a short. Start with 5v on the other side of the resistor to eliminate variables...

    Connect pin 20 to 5V (it powers the chip's logic). I noticed that I forgot that on the wiki...fixed, thx!

    ReplyDelete
  18. Thanks a lot man!! :) It works. No smoke either lol
    Thanks for the valuable resource and your time writing it up.
    http://www.youtube.com/user/kitecraft#p/a/u/0/TwZKEuzunWM

    Cheers
    T.

    ReplyDelete
  19. A better vid :)
    http://www.youtube.com/watch?v=Qsbyn2qO8ts

    I modified your code by removing the FlickerBrightness class and moveing Data1 and Data2 into the M5451 class itself. I also added setBit(int), readBit(int), and clearBit functions as well as allOn() and allOf (and a few others). Then created a new Leds class that inherits it. In there I wrote funcs for differnt patterns including randomFill and randomWipe and SpriteLR, SpriteRL.
    Next up is to add a RF (radio freq.) receiver and write the control program in Ruby to broadcast the signal from a computer :)
    Thanks again for the great help.
    I'll send ya my code if ya want it.

    Cheers,
    T.

    ReplyDelete
  20. looks great! You should put the code up online. Can you attach it as a comment to your youtube video. I'd love to add parts of what you've written to the library I've created at http://code.google.com/p/arduino-m5451-current-driver/. But there may be license issues unless you are putting your code in the public domain.

    ReplyDelete
  21. Hi,
    I am trying to get the MMA7660FC working with the arduino but no luck. I am new to I2C and my arduino is an atmega8. How can I contact you (any contact mail id?)

    ReplyDelete
  22. How in the world did you learn that I have MMA7660FC code running?!? Send me an email at this name at gmail dot com.

    ReplyDelete
  23. Hi,
    May be you are have simple code to turn on 1 or 2 leds? Still stuck to get it working. Here is Arduino Nano v3 and M5451 single chip.

    ReplyDelete
  24. Seem some issue while compiling code, error at line 151. Updted to following to make it work:
    if (i<32) {serDataVal = (a&1); a>>=1;}

    ReplyDelete
  25. how to turn on the LEDs in series? Each previous LED need to be ON.

    ReplyDelete