Perl, Embedded C, and a little more on the 8×8 LED matrix

So while I’m waiting for funding and time to get my afformentioned 8×8 led matrix parts, I’ve been spending my time working on a basic driver to test the apparatus via a scrolling text marquis. First of all, it’s funny how a quick and dirty chunk of test code can quickly turn into an interesting and in-depth project! I started out by designing a “library” of hexadecimal values that represent each letter, (for example A == 0x1F,0×28,0×48,0×28,0x1F, which is the columnar value that draws an A on the matrix) and then designing a C program that writes the text to the matrix and scrolls it across the screen using the atmega328P’s timers to multiplex and scroll the message (insert link to code here!). Essentially, this algorithm operates by using one timer output compare interrupt to update the display periodically, and another to increment through the message array periodically. This was simple enough (actually more simple than I had anticipated, which excites me). The problem was creating the array of hex values that contained the message was insanely tedious. For each letter in the message, I had to manually input up to 6 hex values which leaves quite a bit of room for error, and sore wrists. So I figured there had to be an easier way to go about this. What I wanted to do was create a program that took a string of text, encoded it into the hex value array, and then plugged the resulting value into the array that was read from. I didn’t want to do this in the main code as micros only have a limited amount of program space and I didn’t want to bog the program down with something that only had to be run once. So I figured that a script would do the trick. I read somewhere once (wikipedia I think) that Perl was originally designed for working with text files, so I figured that it was a natural choice for my needs. I had never used Perl before and was only barely familiar with the syntax and datatypes, but after about an hour of googling and a quick forum post I had successfully designed a script that read a string in from an input file, used a hash to translate the values into the hex strings, and stuck the whole thing in as a properly formatted C array into the code. Basically, thanks to Perl, I can take a string from an input file, properly update and compile my C source file and load it up all in a few keystrokes! Once again, I’m pretty excited. I feel that this is what computer programming is really all about, taking tedious and repetitive tasks and creating new ways to NOT do them.

The Perl Script:

#!/usr/bin/env perl
################################################################################
# marqconv.pl is a perl hack that I threw together to modify the C source file
# marquis.c This script reads a line in from a text file, translates it through
# a hash into an array of hexadecimal values and then writes these array
# elements to the appropriate array in marquis.c
# basically this is the most awesome thing ever written.
################################################################################
use strict;
use warnings;
use Tie::File;

# Hash relating characters to their hex array values for use in the C program.
my %hashkey = (
	" " => "0x00,0x00,0x00,0x00,				/* space */",
	"A" => "0x1F,0x28,0x48,0x28,0x1F,0x00,0x00,		/*   A   */",
	"B" => "0x7F,0x49,0x49,0x36,0x00,0x00,			/*   B   */",
	"C" => "0x3E,0x41,0x41,0x41,0x22,0x00,0x00,		/*   C   */",
	"D" => "0x7F,0x81,0x81,0x3E,0x00,0x00,			/*   D	 */",
	"E" => "0x7F,0x49,0x49,0x49,0x00,0x00,			/*   E   */",
	"F" => "0x7F,0x48,0x48,0x40,0x00,0x00,			/*   F   */",
	"G" => "0x3E,0x41,0x41,0x45,0x26,0x00,0x00,		/*   G   */",
	"H" => "0x7F,0x08,0x08,0x08,0x7F,0x00,0x00,		/*   H	 */",
	"I" => "0x41,0x41,0x7F,0x41,0x41,0x00,0x00,		/*   I	 */",
	"J" => "0x02,0x01,0x01,0x3E,0x00,0x00,			/*   J   */",
	"K" => "0x7F,0x08,0x14,0x22,0x41,0x00,0x00,	        /*   K	 */",
	"L" => "0x7F,0x01,0x01,0x01,0x00,0x00,			/*   L   */",
	"M" => "0x7F,0x20,0x10,0x20,0x7F,0x00,0x00,		/*   M   */",
	"N" => "0x7F,0x20,0x08,0x02,0x7F,0x00,0x00,		/*   N   */",
	"O" => "0x1C,0x22,0x41,0x41,0x22,0x1C,0x00,0x00,	/*   O   */",
	"P" => "0x7F,0x48,0x48,0x60,0x00,0x00,			/*   P   */",
	"Q" => "0x1C,0x22,0x41,0x49,0x22,0x1D,0x00,0x00,	/*   Q   */",
	"R" => "0x7F,0x48,0x4C,0x33,0x00,0x00,			/*   R   */",
	"S" => "0x32,0x49,0x49,0x26,0x00,0x00,			/*   S   */",
	"T" => "0x40,0x40,0x7F,0x40,0x40,0x00,0x00,		/*   T   */",
	"U" => "0x7E,0x01,0x01,0x01,0x7E,0x00,0x00,		/*   U   */",
	"V" => "0x7C,0x02,0x01,0x02,0x7C,0x00,0x00,		/*   V   */",
	"W" => "0x7F,0x02,0x04,0x02,0x7F,0x00,0x00,		/*   W   */",
	"X" => "0x63,0x14,0x08,0x14,0x63,0x00,0x00,		/*   X   */",
	"Y" => "0x40,0x20,0x1F,0x20,0x40,0x00,0x00,		/*   Y   */",
	"Z" => "0x43,0x45,0x49,0x51,0x61,0x00,0x00,		/*   Z   */",
	"." => "0x02,0x00,0x00,					/*   .   */",
	"!" => "0x7C,0x00,0x00,					/*   !   */",
	"?" => "0x5A,0x50,0x70,0x00,0x00,			/*   ?   */",
	"0" => "0x3E,0x43,0x49,0x61,0x3E,0x00,0x00,		/*   0   */",
	"1" => "0x21,0x7F,0x01,0x00,0x00,			/*   1   */",
	"2" => "0x21,0x43,0x45,0x49,0x31,0x00,0x00,		/*   2   */",
	"3" => "0x22,0x14,0x94,0x55,0x22,0x00,0x00,		/*   3   */",
	"4" => "0x18,0x28,0x48,0x7F,0x08,0x00,0x00,		/*   4   */",
	"5" => "0x79,0x49,0x49,0x4F,0x00,0x00,			/*   5   */",
	"6" => "0X3e,0X49,0x,49,0x26,0x00,0x00,			/*   6   */",
	"7" => "0x40,0x47,0x58,0x60,0x00,0x00,			/*   7   */",
	"8" => "0x36,0x49,0x49,0x36,0x00,0x00,			/*   8   */",
	"9" => "0x30,0x48,0x48,0x3F,0x00,0x00,			/*   9   */",
);

# specify file to be edited.
my $codefile = 'marquis.c';

# specify the point in the file to be edited.
my $arraystr = 'volatile const uint8_t marquis[] = {';

# Open up my input files
open(my $in, "<", "string.txt") or die "Cuidado! Piso Mojado: $!";
tie my @code, 'Tie::File', $codefile or die "Cuidado! Piso Mojado: $!";

# Read in the string from the input file and chomp off newline
chomp(my $string = <$in>);

# convert string to upper case to make things easier
$string = uc($string);

# convert string to array of characters
my @line = split('',$string);

# initialize an empty array
my @datalines = ();

# push a leading space into the array
push(@datalines,$hashkey{" "});

# take each character and push it's hex matrix to the array
foreach (@line)
{
	push(@datalines,$hashkey{$_});
}

# add a trailing space with no trailing comma.
push(@datalines,"0x00,0x00,0x00,0x00");

# close all files
close($in);

# calculate the start and length for the splice function.
my $start = (grep {$code[$_] eq $arraystr} 1..$#code)[0]+1;
my $length = (grep {$code[$_] eq '};' and $_>$start} 1..$#code)[0] - $start;

# splice our output into the file to be modified.
splice @code, $start, $length, @datalines;

# close file.
untie @code;

and the C code

/*******************************************************************************
 * File: marquis.c
 * Owner: Cyrus Metcalf (cyrus.metcalf@gmail.com)
 * Date: October 25, 2010
 *
 * Marquis.c is a simple test driver for the atmega328P microcontroller
 * to run a scrolling marquis across an 8x8 LED matrix of my own design.
 *******************************************************************************/

//=====================
//HEADERS
//=====================
#include
#include

//======================
//CONSTANTS
//======================

//#define MAX 77 //length of Marquis, manually calculated.

#define MAX(x) (sizeof(x) / sizeof * (x))

//======================
void ioinit(void);      	//Initializes IO
void timer_0_set_up(void); 	//Initialize Timer 0
void timer_2_set_up(void); 	//Initialize Timer 2
//======================

/* Array autogenerated by marqconv.pl  */
volatile const uint8_t marquis[] = {
0x00,0x00,0x00,0x00,				/* space */
0x7F,0x08,0x08,0x08,0x7F,0x00,0x00,		/*   H	 */
0x7F,0x49,0x49,0x49,0x00,0x00,			/*   E   */
0x7F,0x01,0x01,0x01,0x00,0x00,			/*   L   */
0x7F,0x01,0x01,0x01,0x00,0x00,			/*   L   */
0x1C,0x22,0x41,0x41,0x22,0x1C,0x00,0x00,	/*   O   */
0x00,0x00,0x00,0x00,				/* space */
0x1F,0x28,0x48,0x28,0x1F,0x00,0x00,		/*   A   */
0x7F,0x48,0x4C,0x33,0x00,0x00,			/*   R   */
0x3E,0x41,0x41,0x41,0x22,0x00,0x00,		/*   C   */
0x7F,0x08,0x08,0x08,0x7F,0x00,0x00,		/*   H	 */
0x7F,0x01,0x01,0x01,0x00,0x00,			/*   L   */
0x41,0x41,0x7F,0x41,0x41,0x00,0x00,		/*   I	 */
0x7F,0x20,0x08,0x02,0x7F,0x00,0x00,		/*   N   */
0x7E,0x01,0x01,0x01,0x7E,0x00,0x00,		/*   U   */
0x63,0x14,0x08,0x14,0x63,0x00,0x00,		/*   X   */
0x7C,0x00,0x00,					/*   !   */
0x00,0x00,0x00,0x00
};

volatile uint8_t position = 0;

int main (void)
{
	ioinit();
	timer_0_set_up();
	timer_2_set_up();

	sei();

	while(1)
	{
		//wait for interrupt.
	}

	return(0);
}

void ioinit (void)
{
    //1 = output, 0 = input
    DDRB = 0b11111111; //All outputs
    DDRC = 0b11111111; //All outputs
    DDRD = 0b11111111; //PORTD (RX on PD0)
}

/* Timer 0 output compare interrupt every 18 Hz to scroll Marquis */
void timer_0_set_up(void)
{
	TIMSK0 = _BV(OCF0A); //enable output compare interrupt
	TCCR0A = _BV(WGM01); //enable CTC mode
	TCCR0B = _BV(CS02);  //set prescaler to clk/256
	OCR0A  = 217;	     //increment every 18Hz.
}
/* Timer 2 output compare interrupt every 10Hz to multiplex the display */
void timer_2_set_up(void)
{
	TIMSK2 = _BV(OCF2A); //enable output compare interrupt
	TCCR2A = _BV(WGM01); //enable CTC mode
	TCCR2B = _BV(CS02) | _BV(CS01); //set prescaler to clk/1024
	OCR2A  = 98;	     //increment every 10Hz
}

ISR(TIMER0_COMPA_vect)
{
	/* increment the position to scroll the text */

	if ((position + 7) == MAX(marquis))//prevent array overflow, restart marquis
	{
		position = 0;
	}
	position++;
}
ISR(TIMER2_COMPA_vect)
{

	/* multiplex the LED Matrix and display the value
	 * stored to PortB on the given column*/

	for (uint8_t offset = 0; offset < 8; offset++)
	{
		PORTD = offset;
		PORTB = marquis[position + offset];
	}
}

Leave a Reply