NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Trouble Controling each LED in a multi-panel LED array project from the PC

March 01, 2010
by brockmjp
brockmjp's Avatar

I've constructed a three panel LED array and it works fine with the software provided in the tutorial downloads. The thing is, I want to be able to control each LED in the array from my PC, so I'm writing my own code. My idea is to have a listener (the easy part) that will send a string of 300 1's and 0's to the master mcu which will parse that, sending 100 to each of the slaves. The slaves would parse the 100 into 5 sets of 20 (Row0 to Row4). Then the slave would drive the pins depending on the sequence for each row (i.e. 1 = 'on', 0 = 'off'). This way I can change fonts or create effects on the fly without having to re-program the mcus. Sounds simple right?

I'm starting with the code for the slaves, but I seem to be having difficulty accessing elements of a char array from within a for loop. The problem only seems to happen in the micro controller, as I'm able to run a similar piece of code on a Linux machine and get the expected result (with print statements substituted for pin changes).

The example below shows how I would loop through the first 20 characters (Row0) and set the column wires high for the odd numbered LED columns (while Row0 is set low) for each LED with a '1' in that position. If I can get this to work, it should be easy to go back and reverse the voltage and do the even columns, and then do each of the other rows the same way (resetting all the drivers between each pass).

First, here is the code that works when compiled in Linux with cc compiler. Then I'll show the code for the micro controller that fails and where I think the problem is.

#define F_CPU 14745600

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <inttypes.h>

main(){
    while (1) {
        // the sequence in array x represents the top row of 'HELLO'
        char x[] = { '1','0','1','0','1','1','1','0','1','0','0','0','1','0','0','0','1','1','1','0','\0' };

        int column_wire; // 0-9
        int i;
        for(i=0; i<20; i=i+2) { 
            if (x[i] == '1') {
                column_wire = floor(i/2);
                if ( column_wire < 2 ) {
                    printf("LED column %d on column wire %d on pin PC%d in row 0 is on\n", i, column_wire, (4 + column_wire) );
                } else {
                    printf("LED column %d on column wire %d on pin PD%d in row 0 is on\n", i, column_wire, (column_wire -2) );
                }
            }
        }
        break;          
    }
}

This gives the expected result for the odd numbered columns (note the column numbers are zero based, that's why I call them odd i.e. column 2 is the third column):

LED column 0 on column wire 0 on pin PC4 in row 0 is on
LED column 2 on column wire 1 on pin PC5 in row 0 is on
LED column 4 on column wire 2 on pin PD0 in row 0 is on
LED column 6 on column wire 3 on pin PD1 in row 0 is on
LED column 8 on column wire 4 on pin PD2 in row 0 is on
LED column 12 on column wire 6 on pin PD4 in row 0 is on
LED column 16 on column wire 8 on pin PD6 in row 0 is on
LED column 18 on column wire 9 on pin PD7 in row 0 is on

However, when I load the following code on the mcu, the LEDs do not light up. I got tired of plugging in all the wires from a panel, so I have a small test setup of LEDs consisting of the first four LEDs of Row0.

#define F_CPU 14745600

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
// PIN DEFINITIONS: 
//
// PB1 - ROW 0
// PC0-3 - ROW 1-4
// PC4-5, PD0-7 COL 0-9

main(){
    while (1) {
        // the sequence in array x represents the top row of 'HELLO'
        char x[] = { '1','0','1','0','1','1','1','0','1','0','0','0','1','0','0','0','1','1','1','0','\0' };

        DDRB |= (1<<PB1);   
        PORTB &= ~(1<<PB1); // set low so we can light up the odd numbered LED columns (0,2,4,...,18) in row0 where specified in x input string by setting columns high

        int column_wire; // 0-9
        int i;
        for(i=0; i<20; i=i+2) { 
            if (x[i] == '1') {
            column_wire = floor(i/2);
            if ( column_wire < 2 ) {
                        DDRC |= ( 1<<(PC4 + column_wire) )
                PORTC |= ( 1<<(PC4 + column_wire) );
            } else {
                        DDRD |= (1<<( PD0 + (column_wire - 2) ) );
                PORTD |= (1<<( PD0 + (column_wire - 2) ) );
            }
            }
        }
        DDRB &= ~(1<<PB1);      
        DDRC &= ~((1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3)|(1<<PC4)|(1<<PC5));
        DDRD &= ~((1<<PD0)|(1<<PD1)|(1<<PD2)|(1<<PD3)|(1<<PD4)|(1<<PD5)|(1<<PD6)|(1<<PD7)); 
    }
}

The funny thing is that I know that the loop is working and I know that the conditional statements work except for the one that checks the value of x[i]. I know this, because if I change the i in just that statement to one of: 0,2,4,6,8,12,16 or 18, then the first and third LEDs will be on. Any other number and all are off. This is just what I would expect if the value of i ( just for the if x[i] == '1' statement ) was any of those numbers, but it doesn't work if I use x[i] in the loop. Furthermore, if I remove the loop and set i = 0 then the first LED is on, or if I set i = 2 then the third LED is on as expected. So the loop works, and the x[i] == '1' conditional works, but they DON'T work together in the mcu while they DO work together just fine on my Linux machine!!!!!

I've exhausted my Google searches trying to figure this out. If anyone could enlighten me on how to get this loop to work, I would be grateful. I would be happy to share the final code (including the listener) on the forum when/if I ever get it figured out.

Thanks for your time and consideration,

Joel

March 01, 2010
by bretm
bretm's Avatar

With the default makefile that comes with most of the projects, array initializers don't get included in the .hex file that is produced. You either need to include the ".data" section of the code by adding "-j .data" to the avr-objcopy command, or you need to define your array using the PROGMEM macro so that the array initializer values are include in program memory instead of data memory. See this for an example.

March 01, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Brockmjp,

Bretm is right. You are getting the unexpected behaviour because static initialization of arrays is a bit tricky when the program and RAM memory spaces are different. There is a pretty good discussion about it in this forum thread

Humberto

March 02, 2010
by brockmjp
brockmjp's Avatar

Thanks for the replies. I tried including "-j .data" to the avr-objcopy command and it worked. I guess I don't quite understand initialization of arrays. The observations I made in my first post suggest that each element was present in array x, since I was able to access any element as long as I set a static value for i. It just didn't work in the loop, so I thought it might be some kind of scope issue. Anyway, I'm happy its working, and setting the string in the code is just for development purposes.

-Joel

March 27, 2010
by brockmjp
brockmjp's Avatar

This has been a great learning experience. I can now send a string of 1's and 0's to create any pattern I want on the marquee. This is obviously useful for creating graphics on the marquee, but it also allows you to animate the marquee arbitrarily (e.g. new fonts, special effects etc.) from the pc without having to reprogram the master mcu.

No changes to the slave program were necessary, I just made a few modifications to the ledarray_master.c program. I first moved the do_scrolling_display() call into the while loop of the main block. Then I added a line below that to call my new function do_pixel_pattern(). There is already a switch built into the do_scrolling_display function so that when an 'a' character is received, the function returns to the while loop in main and then enters the do_pixel_pattern function. I put a similar switch in the do_pixel_pattern triggered by a 'b' character, so I can go back to the default scrolling mode.

Since the do_scrolling_display function is the default, you don't have to change the way you send data to the marquee if you want to keep using the built in scrolling. To use the new function, you send an 'a' to switch modes, and then send a string of 1's and 0's representing each row in the marquee. I have a three panel marquee, so the first sixty characters in the string are row0, the next sixty are row1, etc. To switch back to scrolling mode, send a 'b'.

Here are the modifications to the original ledarray_master.c program:

1. Added call to math header ( #include <math.h> ) for the floor function used in do_pixel_pattern()
2. Added global variable definition for total number of LEDs ( #define LEDS (ROWS * COLS) )
3. Added do_pixel_pattern() function as follows

void do_pixel_pattern(){
    // This is the 'a' mode.  If a 'b' is received, the function will return to main and start the do_scrolling_display function
    blank_data();
    char buf[LEDS];
    char x;
    uint16_t bufpos=0;
    uint16_t i; // Current position in the entire sign. An integer between 0 and the total number of LEDs in the sign minus one.
    uint8_t row; // Current row in the entire sign (i.e. 0, 1, 2, 3 or 4).
    uint8_t col; // Current column in the entire sign.  An integer between 0 and the total number of columns in the sign minus one. (e.g. first column of second array is col = 20, first column of third array is 40)
    while (1) {
        x = uart_read();

        if(x=='a') {
            //ignore
        }else if(x=='b') {
            // switch to the 'b' mode (do_scrolling_display)
            blank_data();
            return;
        }
        if (x == '\r' || x == '\n') {
            blank_data();
            // render
            for(i = 0; i < LEDS; i++) {
                row = floor( i/COLS );
                col = i - (row * COLS);
                if (buf[i] == '1') { 
                    data_set(row,col,1);
                }else{
                    data_set(row,col,0);
                }
                buf[i] = '0';
            }
            // clear buffer for next line
            bufpos = 0;
            uart_write('n');
        }else{
            // add to buffer
            buf[bufpos] = x;
            if(bufpos < LEDS) bufpos++;
        }
    }
}

If you try this out on your own marquee, here is a simple test:

Use putty (or some other method to make your serial connection).  
Hit the 'a' key.  Then hit the '1' key and press Enter. You should see the LED in row0, column0 light up.
Now hit '0', then '1', then Enter. The light will move to column1.
Next hit '0' two times, then '1' and Enter. The light will move to column2.
You can repeat this pattern adding more '0' before the '1' and Enter to make the light move through the entire marquee column by column row by row.

Here is Perl script (Windows) that will do the test above and then print a smiley face on the left side of the marquee (5 row, 20 column, 3 panel). Check your COM port number and edit if necessary:

#!/usr/bin/perl
use strict;
use warnings;
use Win32::SerialPort;
my $serial = Win32::SerialPort->new('COM5') || die "Can't make serial port connection at COM5: $!\n";
$serial->baudrate(115200);
$serial->parity("none");
$serial->databits(8);
$serial->stopbits(1);
$serial->write_settings;

$serial->write('a');
$serial->write("1");
$serial->write("\n");
sleep(1);
$serial->write("01");
$serial->write("\n");   
sleep(1);
$serial->write("001");
$serial->write("\n");   
sleep(1);

# Don't try to send long strings all at once
# Much better to split into an array and send one character at a time
my $smiley = "001111111100000000000000000000000000000000000000000000000000010010010010000000000000000000000000000000000000000000000000010100001010000000000000000000000000000000000000000000000000010011110010000000000000000000000000000000000000000000000000001111111100000000000000000000000000000000000000000000000000";
foreach ( split(//,$smiley) ) { $serial->write($_) }
$serial->write("\n");
sleep(10);
$serial->write("0\n"); 
undef $serial; # close the connection
March 28, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi brockmjp,

I'm glad you have your project working, and that you learned a lot in the process. We would love to see some pictures of it working if you have them!

Humberto

April 04, 2010
by brockmjp
brockmjp's Avatar

A few days after Halloween, I went out to Walmart to see what I could pick up for 75% off. I bought a bunch of boxes of purple LED lights. I didn't know what I was going to do with them at the time, but I figured I would think of something. Then I stumbled on Rick_S's LED marquee design using Xmas LEDs which are pretty much the same thing. I basically followed Rick's instructions to construct the marquee except I used a full length piece of laminate flooring (free from Craig's list) and drilled 300 holes instead of 120. The wire stripping and soldering was a pretty big job. One thing that surprised me when I completed this project was the fact that no external power is needed except for what I get from the USB cable. The first picture shows how I can turn on all 300 LEDs. No battery, no walwart, no voltage regulator, just the USB power. I even tried it with a wireless USB hub (iogear) and that works too.

all_on

The next picture shows the smiley face I referred to in my last post. Not a very good smiley face, but 5 rows is kinda limiting.

all_on

Post a Reply

Please log in to post a reply.

Did you know that you can control multiple LEDs from one microcontroller output? Learn more...