The standard way to drive large numbers of LEDs is to multiplex them and scan through them to have the LEDs look like they are all on.
This is the code that I use for driving an 8x8 led matrix I built a few weeks ago. Note that there are 64LEDs. Without multiplexing, this would be way over the current specs for Atmega CPUs.
This is easily extended by using a 74154 4-16 bit decoder and driving the columns or rows using 4-bits instead of the 8-bits that I use. Also, the LEDs do not have to be a matrix, any organization will do as long as your program is set up to turn on/off the correct LED when you want.
// Matrix image values
unsigned char POWER[8]={1,2,4,8,16,32,64,128}; // The first 8 powers of 2.
unsigned char V[8]={0xF1,0x73,0x37,0x1F,0xF8,0xEC,0xCE,0x8F}; // An image of a pinwheel.
unsigned char U[8]={0x8F,0xCE,0xEC,0xF8,0x1F,0x37,0x73,0xF1}; // A rotated version of the pinwheel.
unsigned char D[8]; // Data to be transfered to the matrix.
int counter;
int load=0;
unsigned char i;
unsigned char j;
void setup()
{
Serial.begin(9600);
// set PORTA and PORTC as output.
DDRB = 0xff;
DDRC = 0xff;
for(j=0;j<8;j++)
{
D[j]=V[j];
}
//Initialize the matrix element loop counters.
i = 0;
j = 0;
}
void loop()
{
unsigned char MX; // 8-bit column data.
unsigned char MY; // 8-bit row data.
unsigned char c; // Utility counter.
// Set up the data.
MX=POWER*; // Set the scan column.*
_ MY=D*; // Set the row data._
_// Transfer the data to the appropriate column and row registers._
_ PORTC = MX; // Set a single column bit to high._
_ PORTB = ~(MY); // Note! Invert the column data so current will flow from PortC to PortB._
_ // LEDS are diodes, so we do not need to worry about current flow in the*_
* // opposite direction. I.E. A high bit on PORTB coupled with a low bit on*
* // PORTC will do nothing.*
* delay(0); // A delay of 0 is required for matrix stability, otherwise random transient*
* // events can be displayed.*
// Select the next column.
* i=i++;*
* if ( i == 8) // If the column selection has gone past the end, start over.*
* {*
* i=0; *
* counter++; // Count the number of natrix refreshes.*
* if( counter==32) // If enough refreshes have occurred, change the display data.*
* {*
* counter=0;*
* load=1-load; // load alternates between 0 and 1.*
* if( load==0) // If load is 0, set the data to V.*
* {*
* for(c=0;c<8;c++)*
* {*
* D*
c*_ <em>*=V[c]; } } else { for(c=0;c<8;c++) // If the load is 1, set the data to U. { D[c]=U[c]; } } } } }*</em> _*