Wiblocks --- PICO-LED

PICO-LED Matrix

The PICO1TR-LED-L and PICO1TR-LED-S are LED matrix boards that are designed to interface with the PICO1TR .

To control the LED matrix two libraries are provided. One library provides generic functions and data structures. The other provides board specific data structures and constants such as the number of rows, number of columns and pin connections.

The ascii2led script provides a way to define matrix layouts using an ASCII configuration file.

The code below was used to create the video (above). The code displays five characters (different size squares) and then the eight characters that spell WIBLOCKS.

extern "C" 
{
#include <PICO1TR.h>
#include <PICO1TR_LED_S.h>
}
 
void setup() { 
  Serial.begin(19200);
  led_init(100); // 100uS on-time (min)
}

void loop() {
  display_chars(8, 5, 3); // display squares
  display_chars(0, 8, 1); // display wiblocks
}

ASCII2LED

The ascii2led utility converts ASCII representations of the LED matrix into a C data structure. The ASCII pictures consist of space and non-space characters. A space character defines an LED as off and a non-space character defines an LED as on. ascii2led packs the bit data into an array. The character array and compiler constants are output to a .c and .h file.

Configuration File Snippet

dir = /local/pub/arduino-0017/hardware/libraries/PICO1TR_LED_L/
c_file  = PICO1TR_LED_L_char_data.c
h_file  = PICO1TR_LED_L_char_data.h
num_col = 8
num_row = 8

[W]
**    **   
**    **
** ** **
** ** **
** ** **
** ** **
 ******
  ****

Generated H File

#ifndef __PICO1TR_LED_L_CHAR_DATA_H__
#define __PICO1TR_LED_L_CHAR_DATA_H__
///
/// generated by ascii2led
///

extern const uint8_t led_char[][8];

#define LED_CHAR_OFFS 87
#define LED_LAST_ORD  11

#endif

Generated C File

///
/// generated by ascii2led
///

#include <avr/pgmspace.h>

uint8_t led_char[][8] = 
{
   {0b11000011, 0b11000011, 0b11011011, 0b11011011, 
    0b11011011, 0b11011011, 0b01111110, 0b00111100},
   {0b11111111, 0b11111111, 0b00011000, 0b00011000, 
    0b00011000, 0b00011000, 0b11111111, 0b11111111},
   {0b11111110, 0b11000011, 0b11000011, 0b11111110, 
    0b11111110, 0b11000011, 0b11000011, 0b11111110},
   {0b11000000, 0b11000000, 0b11000000, 0b11000000, 
    0b11000000, 0b11000000, 0b11111111, 0b11111111},
   {0b01111110, 0b11111111, 0b11000011, 0b11000011, 
    0b11000011, 0b11000011, 0b11111111, 0b01111110},
   {0b01111111, 0b11111111, 0b11000000, 0b11000000, 
    0b11000000, 0b11000000, 0b11111111, 0b01111111},
   {0b11000110, 0b11001100, 0b11011000, 0b11111000, 
    0b11111000, 0b11001100, 0b11000110, 0b11000011},
   {0b01111111, 0b11111111, 0b11000000, 0b11111110, 
    0b01111111, 0b00000011, 0b11111111, 0b11111110},
   {0b11111111, 0b11111111, 0b11111111, 0b11111111, 
    0b11111111, 0b11111111, 0b11111111, 0b11111111},
   {0b00000000, 0b01111110, 0b01111110, 0b01111110, 
    0b01111110, 0b01111110, 0b01111110, 0b00000000},
   {0b00000000, 0b00000000, 0b00111100, 0b00111100, 
    0b00111100, 0b00111100, 0b00000000, 0b00000000},
   {0b00000000, 0b00000000, 0b00000000, 0b00011000, 
    0b00011000, 0b00000000, 0b00000000, 0b00000000}
};

ascii2led Listing

#!/usr/bin/perl

use strict;
use warnings;
use Carp;

use Data::Dumper;

use constant DEBUG => 1;

# Character definitions consist of a character header followed by
# lines containing data for a single row in the matrix. Each line
# consists of a series of non-space characters and spaces. An
# non-space character indicates the should be 'on', a space indicates
# 'off'.

# The following parameters can be set in the configuration file using
# NAME = VALUE lines ...

# c_file ........ Name of the .c file to generate
# h_file ........ Name of the .h file to generate
# dir ........... Directory to place the .c and .h files
# num_col ....... Number of columns in the LED matrix
# num_row ....... Number of rows in the LED matrix

my %Cfg;  # all of the date read from the configuration file

my $Char; # reference to the current character array

@ARGV = qw(pico1tr_led_s.cfg) if $#ARGV == -1;

while (<>) {
    s/\s+$//; # remove trailing spaces
    next if /^\s*\#.*/;  # Skip comment lines
    last if /^__END__$/; # Skip lines after the end marker
    if (/^\s*\[(.)\]$/) {
	#
	# Start a new character array
	#
	$Cfg{_offs} = ord $1 unless defined $Cfg{_offs};
	my $char = [];
	push @ { $Cfg{chars} }, $char;
	$Char = $char;
    } elsif (s/\s*=\s*/ /) {
	#
	# key-value pair
	#
	s/^\s+//;
	s/\s+$//;
	my ($k,$v) = split;
	$Cfg{$k} = $v;
	$Cfg{last_row} = $Cfg{num_row} - 1 if $k eq 'num_row';
	$Cfg{last_col} = $Cfg{num_col} - 1 if $k eq 'num_col';
	$Cfg{_num_bits} = $Cfg{num_row} * $Cfg{num_col}
  	  if defined $Cfg{num_row} && defined $Cfg{num_col};
    } elsif (defined $Char) {
	#
	# change all non-space character to 1's
	# change all spaces to 0's
	#
	s/\S/1/g;
	s/\s/0/g;
	#
	# split the string into characters
	# and pad the right side with 0's
	# 
	my @bits = split //;
	push @bits, '0' while $#bits < $Cfg{last_col};
	#
	# @bits now contains the proper number of
	# binary digits. Add the bits to the current
	# chracter array.
	#
	push @$Char, @bits;
	#
	# if all the LED row data has been generated
	# than save the character and reset
	# 
	$Char = undef if $#$Char == $Cfg{_num_bits} - 1;
    }
}

#
# output the C file
#
open(OUT, ">$Cfg{c_file}") || die "(ascii2led) Could not open $Cfg{c_file} for output: $!";
print OUT << 'END';
///
/// generated by ascii2led
///

#include <avr/pgmspace.h>

END


my @Chars = @ { $Cfg{chars} };
$Cfg{_last_ord}  = $#Chars;
$Cfg{_num_bytes} = int(($Cfg{_num_bits} + 4) / 8);

printf(OUT "uint8_t led_char[][%i] = \n{\n", $Cfg{_num_bytes});
while (@Chars) {
    my @bits = @ { shift @Chars };
    push @bits, 0 while $#bits < $Cfg{_num_bits} - 1;
    push @bits, 0 while ($#bits + 1) % 8 != 0;
    my $line = "   {";
    while (@bits) {
	my @b = splice @bits, 0, 8;
	$line .= sprintf("0b%s%s", join('', @b), $#bits == -1 ? '}' : ', ');
    }
    printf(OUT "%s%s\n", $line, $#Chars == -1 ? "\n};" : ",");
}
close(OUT) || die "(ascii2led) Could not close output file $Cfg{c_file}: $!";


#
# output the header file
#
my $__H__ = sprintf("__%s__", $Cfg{h_file});
$__H__ =~ tr/[a-z]/[A-Z]/;
$__H__ =~ s/\./_/g;
open(OUT, ">$Cfg{h_file}") || die "(ascii2led) Could not open $Cfg{h_file} for output: $!";

printf(OUT "#ifndef %s\n", $__H__);
printf(OUT "#define %s\n", $__H__);

print OUT << 'END';
///
/// generated by ascii2led
///

END

printf(OUT "extern const uint8_t led_char[][%i];\n", $Cfg{_num_bytes});

my @def = qw(LED_CHAR_OFFS _offs 
	     LED_LAST_ORD _last_ord 
	     LED_NUM_ROWS num_row 
	     LED_NUM_COLS num_col);
while (@def) {
    my ($def, $key) = splice @def, 0, 2;
    printf(OUT "#define %s %i\n", $def, $Cfg{$key});
}
printf(OUT "\n\n#endif\n");

close(OUT) || die "(ascii2led) Could not close output file $Cfg{h_file}: $!";

#
# Copy the files to the Arduino directory
#
foreach (map { $Cfg{$_} } qw(c_file h_file)) {
    my $cmd = sprintf("mv %s %s", $_, $Cfg{dir});
    printf("(ascii2led) $cmd\n");
    next if DEBUG;
    system($cmd);
}

# Style (adapted from the Perl Cookbook, First Edition, Recipe 12.4)

# 1. Names of functions and local variables are all lowercase.
# 2. The program's persistent variables (either file lexicals
#    or package globals) are capitalized.
# 3. Identifiers with multiple words have each of these
#    separated by an underscore for readability.
# 4. Constants are all uppercase.
# 5. If the arrow operator (->) is followed by either a
#    method name or a variable containing a method name then
#    there is a space before and after the operator.


    

References

Lancaster, Don (2001) Tech Musings, August, 2001 Retrieved May 2, 2010

wikipedia Charlie-plexing