PINE64::MCP300x CPAN Module

My MCP300x module on CPAN for the PINE64A+ boards

One of the drawbacks, in my opinion, of the raspberry pi and the pine64 A+ is the lack of any analog inputs. To read an analog signal requires an external analog to digital converter chip, and will tie up several of your GPIO lines. My new module has routines to read values on an MCP3004 or MCP3008 10-bit analog to digital converter.

Here is some sample code ->

use PINE64::MCP300x; 
my $adc = PINE64::MCP300x->new(10,12,11,13); 
#5 bits because the first is the start bit
my @ch0 = (1,1,0,0,0); 
for(my $s=0;$s<200;$s++){
my ($reading, $binval, $voltage ) = $adc->read300x(\@ch0, 50, 5.01);
$voltage = sprintf("%.3f", $voltage);
print "binval: $binval\tvoltage: $voltage vdc\n";
usleep(500000);}#end for

Here is the output of the script ->

PINE64::MAX7219

I have had another cpan module published! This one is a driver for a MAX7219 8-digit LED display on the PINE64 single board computer. The video above is a demo of some of the module’s capabilities. It is implemented as bit-banged SPI using the perl programming language.

A very easy to use display, it only uses 3 GPIO pins, and could be used to output the value of sensors, or simple menus for projects.

PINE64::GPIO module published on CPAN

my first perl module published on CPAN

One of my personal goals this year was to publish some of my work that uses perl to experiment with single board computers on CPAN (the Comprehensive Perl Archive Network). I have used perl to make windows applications, database applications, web applications, text processing utilities, inventions….. you name it. However, it is not widely used for single board computers like the raspberry pi. There are a lot of reasons for this. Perl is no longer the cool programming language say, like Python is. Programming in perl is almost an anachronism and dates you. It is relatively slow. Even so, perl is incredibly versatile and powerful and has a devoted following of some of the most brilliant people. Perl isn’t going anywhere.

I have been a consumer of CPAN for nearly 10 years. CPAN is a code repository where programmers share their code so you don’t have to re-invent the wheel in your programs if someone else has already figured out how to implement what you are trying to do.

Steve Bertrand and others have published a ton of useful modules for the raspberry pi family of SBC’s on CPAN. Dude even published a book on indiegogo on pi projects using perl. I am not near as smart as Mr. Bertrand, so I wanted to stay away from publishing my pi modules on CPAN. The idea is to do something that hasn’t been done before. No one like in the whole world seems to be using perl on the Pine64 board (if you google, I am virtually the only person doing it), so I decided to post my modules for the PineA64+.

To publish on CPAN, you have to have a PAUSE account (Perl Authors Upload Server). You have to be ‘in the club’. You basically have to apply for membership stating what you intend to offer. I also gave them the address of this site so they could see some of my work. I didn’t hear anything back for nearly six weeks. I assumed they looked at my projects and declined to grant me access. I finally did get an account and proceeded to package my modules to their requirements and uploaded my first module: a script that controls the GPIO pins on the PineA64 board’s Pi-2 bus. I consider it a tremendous honor to have been accepted.

I have a technology degree with some graduate work in computer science. I work in telecom, however, not as a programmer. Programming, though, is my secret weapon. Paired with the knowledge of electronics hardware and fabrication, I can create just about anything I can think up. It is a thrill to finally get published on CPAN. I will have many more modules for the PineA64 to follow for various sensors, analog to digital converters, displays, etc.

pine64 guinea pig habitat comfort control

pine64 and perl guinea pig habitat environment automation
pine64 and perl guinea pig habitat environment automation

We’ve moved our guinea pig Monster out to the garage.  He’s in no danger of overheating (trust me, he is spoiled rotten!) but it does get a little warm out there.  I used my pine64 to turn on a 20W fan when it rises above a chosen temperature.  Temperature samples are taken with an analog TMP36 sensor which is read by an MCP3004 analog to digital converter.  It also gets pitch dark with the lights out at night, so there is also a photo resistor that is read by the MCP3004 ADC.  When it drops below a certain threshold, it turns on an LED night light for Monster.  I think he likes it.  He has been laying in front of the fan.

Once the ADC reading is taken of the TMP36 sensor, this is how it is converted into degrees Fahrenheit.

my $temperature = ((($voltage_t * 1000) - 500) / 10) * 1.8 + 32;

I am running armbian on my pine64 and have written my own gpio control script in perl.  Here is the perl script that controls Monster’s fan and night light.

#!/usr/bin/perl 
use strict;
require "./MCP3004.pl";

gpio_enable(17, 'out');
gpio_enable(13, 'out');

#get thermostat arg
my $thermostat = $ARGV[0];
if($thermostat eq ''){
	$thermostat = 85;
}#end thermostat


#channels: first bit is set to single, not differential
#5 bits because the first is the start bit
my @ch0 = (1,1,0,0,0);
my @ch1 = (1,1,0,0,1);
my @ch2 = (1,1,0,1,0);
my @ch3 = (1,1,0,1,1);
#differential
my @ch01 = (1,0,0,0,0);

init3004(18,19,24,25);

#loop global variables
my $tmp10 = 0;
my $tavg = 0;
my $bin10 = 0;
my $binavg = 0;
my $fan_f = 0;
my $lt_f = 0;

for(;;){
	$tmp10 = 0;
	$tavg = 0;
	$bin10 = 0;
	$binavg = 0;

	for(my $n=0;$n<10;$n++){ #photoresisitor reading------------------------------------------- my ($reading, $binval, $voltage ) = read3004(\@ch0, 50, 5.01); print "binval: $binval\t$voltage vdc"; $bin10 += $binval; #print RD "$s\n$voltage\n"; #photocell end---------------------------------------------------- #tmp36 temperature reading---------------------------------------- print "\n---------------------------------------\n"; my ($reading_t, $binval_t, $voltage_t ) = read3004(\@ch1, 50, 5.01); #print "binval_tmp: $binval_t\tvoltage_tmp: $voltage_t vdc\n"; my $temperature = ((($voltage_t * 1000) - 500) / 10) * 1.8 + 32; $temperature = sprintf("%5.2f", $temperature); print "temperature: $temperature deg F\n"; #accumulate temp $tmp10 +=$temperature; print "---------------------------------------\n"; #tmp36 end-------------------------------------------------------- #0.5 pause between samples usleep(500000); }#end for 10 #calculate temperature averages $tavg = $tmp10 / 10; if($tavg > $thermostat){
		#turns fan on
		gpio_write(17, 1);
		$fan_f = 1;
	}#end if
	else{
		gpio_write(17, 0);
		$fan_f = 0;
	}#end else

	#calculate light intensity averages
	$binavg = $bin10 / 10;
	if($binavg > 610){
		#turns light on
		gpio_write(13, 0);
		$lt_f = 1;
	}#end if
	else{
		gpio_write(13, 1);
		$lt_f = 0;
	}#end else

	print "\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n";
	print "avg temp: $tavg stat:$fan_f\tavg light: $binavg stat:$lt_f";
	print "\n-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-\n";

	#wait 1 minute
	sleep(60);
}#end outer for

That’s a lot of processing power just to control a light and a fan.  I plan to migrate the function to an arduino nano soon.

pine64 and perl guinea pig habitat environment controls
pine64 and perl guinea pig habitat environment controls

pine64 guinea pig night light
pine64 guinea pig night light

per & pine64 guinea pig automation
perl & pine64 guinea pig automation

armbian for pine64

armbian for pine64 CLI only
armbian for pine64 CLI only

When I first got my 512MB pine64 A64 sbc,  I initially loaded a great community based version of debian with an LXDE desktop.  I was really happy with it.  Recently, I was trying to install i2c-tools and was getting errors.  I then tried to update my system, and could not connect to the repository.  Same for perl  packages using cpan.  After searching for an answer in the pin64 forums, I learned that dude that made the awesome debian img for the pine64 stopped supporting it, and there was a suggestion to migrate to an armbian distro that was cli only.  I really wanted to use my i2c bus, so I burned a not-so-fresh sd card with armbian.

armbian for pine64 initial login via UART0
armbian for pine64 initial login via UART0 on the euler bus

Initially, I was happy.  I really don’t need a desktop environment.  I quickly noticed that my ethernet interface did not work at all.  Once again, I searched the forums, and found a solution.  Using martinayotte posted a solution that worked great for me.

Not very promising that my NIC did not work out of the box, but I am going to give it a fair try.

using pine64 gpio to control a 120VAC light bulb

I’ve used gpio on various SBCs to drive small signal relays  on a few projects, but never any high-current, high-voltage stuff.  The principle is exactly the same, I just hadn’t used it before.  The elegoo 37-sensor kit I just got had a handy break out board for a 10A 250VAC relay.  I used a gpio line on a pine64 to drive the relay coil using a PN2222A transistor circuit.  The script is no different than blinking an LED.

pine64 gpio driving a 10A 250VAC relay circuit using a PN2222A NPN transistor to provide ground

 

 

pine64 turning on and off light bulb

perl script for pine64 relay control

Next, I controlled the relay by reading the voltage drop across a photo resistor voltage divider using an MCP3004 10-bit ADC.  When the OP code from the ADC rose above 400, the light comes on (because it is dark).

Controlling a relay by reading the voltage drop across a photo resistor using an MCP3004 ADC
Controlling a relay by reading the voltage drop across a photo resistor using an MCP3004 ADC

pine64 as a terminal server via UART

Still working on the maker space and expanding my networking lab.  I added a cisco 1721 router with a 10-base T wan interface card and a serial 1T WIC in addition to the built-in fast ethernet interface.  I also added an old cisco 2514 with two built-in AUI (ethernet) interfaces and two serial interfaces.

Cisco 1721 and 2514 routers
Cisco 1721 and 2514 routers

The computer at my work bench doesn’t have a serial interface that I can connect to the routers’ console interfaces, and I was too lazy to go get my usb to serial converter out of my van; especially when I had my pine64 nearby with an rs-232 to TTL converter.  I fired up my pine64 and connected the rs-232 to TTL converter to the UART pins on the pi-2-bus.  The converter and the console port of the routers are both DTE devices, so a null-modem (serial crossover) cable / adapter are necessary.  I hooked it all up like so:

Pine64 as a simple terminal server
Pine64 as a simple terminal server

Here you can see I have my pine64 connected to a cisco console cable via a null modem adapter and a gender changer.  First I had to ssh into the pine.   Now to install screen on my pine64.


sudo apt-get install screen
sudo screen /dev/ttyS2 9600

The second command tells screen to use ttyS2, which is the serial interface that is on the pi-2 bus and use 8-N-1 and 9600 baud. Now I’m in.

cisco router IOS command line interface
cisco router IOS command line interface

perl-based GPIO control for pine64

The pine64, like many single board computers, has a file system interface for its gpio pins.  This means that reading and writing logic levels on a particular gpio pin is a matter of reading and writing to the particular file for that pin.  Interacting with files in this manner is something that perl excels at.  That is one reason why I mostly forego using ready made tools like wiringpi and make my own gpio interface functions.  This also help me to understand the system more completely, and customize according to the way that I think, instead of using another’s canned tools.

I have written a number of perl scripts for the raspberry pi that control the gpio system interface and bit-banged drivers for several popular integrated circuits such as the MCP3004, MCP3008, MCP3208 (all analog to digital converters) , MAX7219, 74HC595, and others.  The pine64 has a (mostly) compatible 40-pin gpio header.  I only had to change one line of my gpio driver to make it work with the pine64.  Here is the perl gpio function file in it’s entirety:

 

#!/usr/bin/perl -w
use strict;

=pod
gpio utilities for pine64
enable, disable, read, write
=cut

#global vars
#array for gpio pin numbers
my @line_nums = (227,226,362,71,233,76,64,65,66,229,230,69,73,80,32,33,72,77,78,79,67,231,68,70,74,75);
my ($iox, $status);

sub gpio_enable{
	my $ind = $_[0];
	$iox = $line_nums[$ind];
	#print "IO line: $iox\n";

	my $gpiosz = @line_nums;	
	#print "gpiosz: $gpiosz\n";

	my $direction = $_[1];
	#print "direction: $direction\n";

	#err chk
	if($ind < 0 || $ind > $gpiosz){
		print "INVALID GPIO RANGE\n";
		exit;
	}#end if invalid index
	if($direction ne 'in' && $direction ne 'out'){
		print "INVALID DIRECTION\n";
		exit;
	}#end if invalid direction
	
	#write gpio pin val to export file
	open(EF, ">", "/sys/class/gpio/export") or die $!;
	print EF $iox;
	close(EF);

	#set direction of gpio pin
	open(GF, ">", "/sys/class/gpio/gpio$iox/direction") or die $!;
	print GF $direction;
	close(GF);

}#end gpio_enable

sub gpio_read{
	#gpio number as arg, returns direction in/out
	my $ind = $_[0];
	$iox = $line_nums[$ind];
	
	my $value = ''; 

	#reads state of gpio pin
	open(GS, "/sys/class/gpio/gpio$iox/value") or die $!;
	while(){ $value = $_; };
	#print "$iox val: $value";
	close(GS);

	if($value == 0 || $value == 1){
		return $value;
	}#end unless
	else{ 
		print "ERROR: Undefined value on GPIO $iox\n";
		exit;
	}#end else
}#end gpio_read

sub gpio_write{
	my $ind = $_[0];
	$iox = $line_nums[$ind];

	my $value = $_[1];

	#write value to gpio pin
	open(GV, ">", "/sys/class/gpio/gpio$iox/value") or die $!;
	print GV $value;
	close(GV);
}#end gpio_write

1;

Very simple (under 80 lines) and very effective.  This has been tested ad infinitum to work as expected.  This gpio function file is also the main building block for my bit-banged drivers.    To demonstrate,  here is an example using a MAX7219 64-LED display driver.  I wrote a custom bit-banged driver using this gpio script.

pine64 perl max7219 driver
pine64 MAX7219 perl based driver

When I write a driver like this, I get the data sheet from the manufacturer, and write functions to set all of the configuration registers and control the IO functions.  I use these 8-position, 7-segment displays frequently in my home brew projects, so I started there with my new pine64.  The driver I wrote for this particular display has some visual effects,  sets the intensity, and has some easy to use functions that can read text files word by word.  I can also handle cascaded displays and a few more novel features that I have not seen anywhere else.  Here is the display driver in its current entirety:

 

#!/usr/bin/perl 
use strict;
use Time::HiRes qw (usleep);
require "/home/debian/gpio/gpio_pine64.pl";

=pod
routines to control a MAX7219 8-digit LED
display driver.  
for now:
CLK: gpio 0
DS : gpio 1
LOAD:gpio 2
data is shifted into regs on rising edge of CLK, 
output displayed on rising edge of LOAD

NOTES:
This version supports cascading several 7219's.  I have
noticed that even with programs that assume only one
7219, that if there is another cascaded, the exact same
output is mirrored on subsequent displays. 

I assume that turn_on, set_intenisty, set_scanlimit, etc
all get shifted into the next chip anyway, however, 
these subroutines take the number of cascaded 7219s as
an argument and shift in and latch the necessary
controls to use the display.  
=cut

#global vars for gpio init function
#init should be called from script
#init7219 (0,1,2);
my ($clk, $ds, $load);

#SUBROUTINE PROTOTYPES
=pod

=cut

############### GLOBAL VARS #####################
#shutdown register 
my @sdreg = (0,0,0,0,1,1,0,0);
my @turn_on = (0,0,0,0,0,0,0,1);
my @turn_off = (0,0,0,0,0,0,0,0);

#set scan limit register 
my @slimreg = (0,0,0,0,1,0,1,1);
#last 3 LSBs scan all 8 digits
my @slregall = (0,0,0,0,0,1,1,1);

#display test register
my @disptstreg = (0,0,0,0,1,1,1,1);

#intensity register
my @intreg = (0,0,0,0,1,0,1,0);

#digit addrs
#first 4 bits D15-D12
#don't care bits, then addr
my @_d0 = (0,1,0,1,0,0,0,1);
my @_d1 = (0,1,0,1,0,0,1,0);
my @_d2 = (0,1,0,1,0,0,1,1);
my @_d3 = (0,1,0,1,0,1,0,0);
my @_d4 = (0,1,0,1,0,1,0,1);
my @_d5 = (0,1,0,1,0,1,1,0);
my @_d6 = (0,1,0,1,0,1,1,1);
my @_d7 = (0,1,0,1,1,0,0,0);

#0-9
my @_1 = (0,0,1,1,0,0,0,0);
my @_2 = (0,1,1,0,1,1,0,1);
my @_3 = (0,1,1,1,1,0,0,1);
my @_4 = (0,0,1,1,0,0,1,1);
my @_5 = (0,1,0,1,1,0,1,1);
my @_6 = (0,1,0,1,1,1,1,1);
my @_7 = (0,1,1,1,0,0,0,0);
my @_8 = (0,1,1,1,1,1,1,1);
my @_9 = (0,1,1,1,0,0,1,1);
my @_0 = (0,1,1,1,1,1,1,0);
my @_all = (1,1,1,1,1,1,1,1);

#alpha chars
my @_a = (0,1,1,1,0,1,1,1);
my @_b = (0,0,0,1,1,1,1,1);
my @_c = (0,1,0,0,1,1,1,0);
my @_d = (0,0,1,1,1,1,0,1);
my @_e = (0,1,0,0,1,1,1,1);
my @_f = (0,1,0,0,0,1,1,1);
my @_g = (0,1,1,1,1,0,1,1);
my @_h = (0,0,1,1,0,1,1,1);
my @_i = (0,0,0,0,0,1,0,0);
my @_j = (0,0,1,1,1,1,0,0);
my @_k = (0,0,1,1,0,1,1,0);
my @_l = (0,0,0,0,1,1,1,0);
my @_m = (1,0,0,1,0,1,0,0);
my @_n = (0,0,0,1,0,1,0,1);
my @_o = (0,0,0,1,1,1,0,1);
my @_p = (0,1,1,0,0,1,1,1);
my @_q = (0,1,1,1,0,0,1,1);
my @_r = (0,0,0,0,0,1,0,1);
my @_s = (0,1,0,1,1,0,1,1);
my @_t = (0,0,0,0,1,1,1,1);
my @_u = (0,0,0,1,1,1,0,0);
my @_v = (0,0,0,1,1,1,0,0);
my @_w = (0,0,0,1,0,1,0,0);
my @_x = (0,0,0,1,1,1,0,1);
my @_y = (0,0,1,1,1,0,1,1);
my @_z = (0,1,1,0,1,1,0,1);

#special chars
my @_dp = (1,0,0,0,0,0,0,0);
my @_sp = (0,0,0,0,0,0,0,0);
my @_dash = (0,0,0,0,0,0,0,1);
my @_comma = (1,0,0,0,0,0,0,0);
my @_period = (1,0,0,0,0,0,0,0);
my @_question = (1,1,1,0,0,0,1,0);
my @_exclaimation = (1,0,1,1,0,0,0,0);

#individual segments
my @_seg_a = (0,1,0,0,0,0,0,0);
my @_seg_b = (0,0,1,0,0,0,0,0);
my @_seg_c = (0,0,0,1,0,0,0,0);
my @_seg_d = (0,0,0,0,1,0,0,0);
my @_seg_e = (0,0,0,0,0,1,0,0);
my @_seg_f = (0,0,0,0,0,0,1,0);
my @_seg_g = (0,0,0,0,0,0,0,1);

#@all_digits is an arra of array references
#representing each digit used to operate
#on each digit 
my @all_digits = (\@_d0,\@_d1,\@_d2,\@_d3,\@_d4,\@_d5,\@_d6,\@_d7);

#@all_segs is an array of array references
#representing each segment of a 7-seg LED array
my @all_segs = (\@_seg_a,\@_seg_b,\@_seg_c,\@_seg_d,\@_seg_e,\@_seg_f,\@_seg_g);

#Hash that maps alphanumeric chars to array ref
my %alphanums = (
	'A' => \@_a,
	'B' => \@_b,
	'C' => \@_c,
	'D' => \@_d,
	'E' => \@_e,
	'F' => \@_f,
	'G' => \@_g,
	'H' => \@_h,
	'I' => \@_i,
	'J' => \@_j,
	'K' => \@_k,
	'L' => \@_l,
	'M' => \@_m,
	'N' => \@_n,
	'O' => \@_o,
	'P' => \@_p,
	'Q' => \@_q,
	'R' => \@_r,
	'S' => \@_s,
	'T' => \@_t,
	'U' => \@_u,
	'V' => \@_v,
	'W' => \@_w,
	'X' => \@_x,
	'Y' => \@_y,
	'Z' => \@_z, 
	'0', => \@_0, 
	'1', => \@_1, 
	'2', => \@_2, 
	'3', => \@_3, 
	'4', => \@_4, 
	'5', => \@_5, 
	'6', => \@_6, 
	'7', => \@_7, 
	'8', => \@_8, 
	'9', => \@_9, 
	'.' => \@_period, 
	'-' => \@_dash, 
	',' => \@_comma, 
	'?' => \@_question, 
	'!' => \@_exclaimation
);#end %alphanums declaration

############### SUBROUTINES #####################
sub shift_in{
=pod
data is shifted in with 16 bit packets: 8-bit segment,
4-bit digit address, 4-bit dont care bits
=cut
	#array ref seg data
	my $leds = $_[0];
	#array ref seg addr
	my $addr = $_[1];
	#number cascaded 7219s
	my $ncas = $_[2];
	#delay in milliseconds
	my $delay = $_[3];
	#latch flag
	my $lf = $_[4];

	#high flag for data gpio line
	my $hf = 0;

	#my $ncp = $ncas * 32;	#min number 32 for 16 clock pulses
	my $ncp = 32;
	##print "ncp: $ncp\n"; 

	#main clock loop
	my $i=0;
	#$state toggles from 1 to 0 needed for clock pulse
	my $state = 0;					
	#ensures first pulse goes from low to high
	my $seed = 3;					

	while($i<$ncp){#correct num clock pulses $state = $seed%2; $seed++; #load data in last 8 clock pulses #make DS high before clock pulse; only on even num #so index array is whole number #MSB read first, then addr, last data if(($i%2) eq 0){ #address D8-D11, and don't care bits D12-D15 if($addr->[$i/2] eq 1 && $i<=14){ gpio_write($ds,1); $hf = 1;#set high flag }#end addr high bit #7-seg data, D0-D7 if($leds->[($i-16)/2] eq 1 && $i > 14){#array ref, light this led up
				#test; set q1 high
				##print "inside data loop, i: $i\tindex: " .(($i-16)/2) . "\n";
				gpio_write($ds, 1);
				$hf = 1;#set high flag
			}#end if build D0-D7

		}#end if even $i 

		#TEST
		#print "i:\t$i\nD:\t$hf\n\n";
	
		#toggle clock pulse
		gpio_write($clk, $state);
		usleep($delay);#sleep .001 sec

		#lower data if high flag set
		if($hf eq 1){
			gpio_write($ds,0);
			$hf = 0;#reset high flag
		}#end if

		$i++;
	}#end while

	#latch output lines
	if($lf eq 1){	
		load();
	}#end latch flag

	gpio_write($clk,0);#set clock pulse low

}#end shift_in

sub load{

=pod
toggles LOAD pin on 7219 to send to output pins.  When the pin goes from low to high, sets output. 
=cut

	#enable XIO-p2
	gpio_enable($load, 'out');

	#ensure it is low
	gpio_write($load, 0);#low

	#go from low to high
	gpio_write($load, 1);#high
	#print "LATCH HIGH\n";
	usleep(500);#pause 0.0005 second

	#reset to low
	gpio_write($load, 0);#low
	#print "LATCH LOW\n";
	
}#end load

sub init7219{
=pod
initializes gpio lines to low
=cut
	$clk = $_[0];
	$ds = $_[1];
	$load = $_[2];

	gpio_enable($clk, 'out');
	gpio_write($clk, 0);
	gpio_enable($ds, 'out');
	gpio_write($ds, 0);
	gpio_enable($load, 'out');
	gpio_write($load, 0);
}#end init

sub print_sentence{
	#currently for a single 7219 array
	my $sentence = $_[0];
	#set to all uppercase letters
	$sentence = uc $sentence;

	#delay between words in microseconds
	my $delay = $_[1];

	#all off flag, set if reading, unset if
	#you want the text to remain on the array
	#if empty, clears the text
	my $cleartxt_flag = $_[2];

	my @words = split / /, $sentence;
	my $numwords = @words;
=pod
	foreach my $word (@words){
		print $word."\n";
	}#end foreach
=cut
	
	#print "sentence: $sentence\nNumwords: $numwords\n";
	
	#loop through words
	for(my $i=0;$i<$numwords;$i++){
		#split word into an array of chars
		my @letters = split //, $words[$i];

		#number of chars in word
		my $numalphanums = @letters;
		#print "num letters: $numalphanums\n";
			
		#first letter
		my $ln = 0;
		
		#reverse @all_digits to display words
		#left to right
		my @rev_segs = reverse @all_digits;

		foreach my $digit (@rev_segs){
		
			#limited to 8 digits for now
			shift_in($alphanums{$letters[$ln]}, $digit, 1, 250, 1 );
			#print "letter: $letters[$ln]\n";
			$ln++;#go to next letter
		}#end letters inner for loop
		
		usleep($delay);
		unless($cleartxt_flag == 1){
			all_off();
		}#end if
	}#end for numwords
}#end print_sentence

sub print_interleaved{
	#takes separate strings for each
	#line of displays. The strings are
	#assumed to fit into the 8-digit
	#line of an led array for a 7219
	my $str1 = $_[0];
	my $str2 = $_[1];

	#upper case
	$str1 = uc $str1;
	$str2 = uc $str2;

	#convert strings to array of chars
	my @s1 = split //, $str1;
	my @s2 = split //, $str2;

	#pad string arrays with spaces
	#if less than 8 chars
	my $s1_len = @s1;
	my $s2_len = @s2;
	#print "s1len: $s1_len\ts2len: $s2_len\n";

	if($s1_len < 8){
		my $nsp = 8-$s1_len;
		for(my $n=$s1_len;$n<8;$n++){
			$s1[$n] = " ";
		}#end for pad w/ spaces
	}#end pad str1 with spaces

	if($s2_len < 8){
		my $nsp = 8-$s2_len;
		for(my $n=$s2_len;$n<8;$n++){
			$s2[$n] = " ";
		}#end for pad w/ spaces
	}#end pad str1 with spaces

	$s1_len = @s1;
	$s2_len = @s2;
	#print "s1len: $s1_len\ts2len: $s2_len\n";

	#reverse @all_digits to display words
	#left to right
	my @rev_segs = reverse @all_digits;

	#init letter number to 0
	my $ln = 0;

	foreach my $digit (@rev_segs){
		shift_in($alphanums{$s2[$ln]}, $digit, 1, 250, 0 );
		shift_in($alphanums{$s1[$ln]}, $digit, 1, 250, 0 );
		load();
		$ln++;
	}#end for
}#end print_interleaved

sub turn_on{
	my $ncas = $_[0];
	if(defined($ncas)){
		#print "tu ncas defined\n";
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@turn_on, \@sdreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				#print "tu load\n";
				load();
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set shutdown register to normal operation
		shift_in(\@turn_on, \@sdreg, 1, 250, 1);
	}#end else
}#end turn_on

sub turn_off{
	my $ncas = $_[0];
	if(defined($ncas)){
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@turn_off, \@sdreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				load();
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set shutdown register to off
		shift_in(\@turn_off, \@sdreg, 1, 250, 1);
	}#end else
}#end turn_off

sub set_scanlimit{
	my $ncas = $_[0];
	if(defined($ncas)){
		#print "sl ncas defined\n";
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@slregall, \@slimreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				load();
				#print "sl load\n";
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set scan limit register
		shift_in(\@slregall, \@slimreg, 1, 500, 1);
		#print "sl 1 chip\n";
	}#end else
}#end set_scanlimit

sub set_intensity{
	#takes string as arg: min, dim, mid, bright, max
	my $intensity = $_[0];
	
	#default to max
	my @intregdata = (0,0,0,0,1,1,1,1);
	if($intensity eq 'min'){
		@intregdata = (0,0,0,0,0,0,0,0);
	}#end if
	if($intensity eq 'dim'){
		@intregdata = (0,0,0,0,0,0,1,1);
	}#end if
	if($intensity eq 'mid'){
		@intregdata = (0,0,0,0,0,1,1,1);
	}#end if
	if($intensity eq 'bright'){
		@intregdata = (0,0,0,0,1,0,1,1);
	}#end if
	if($intensity eq 'max'){
		@intregdata = (0,0,0,0,1,1,1,1);
	}#end if

	#print "Intensity: $intensity\n";
	shift_in(\@intregdata, \@intreg, 1, 250, 1);
}#end set_intensity

sub all_off{
	#clear display
	#call after turned on, and
	#scan reg set to all digits
	my $ncas = $_[0];
	if(defined($ncas)){
		foreach my $digit(@all_digits){
			for(my $ni=0;$ni<$ncas;$ni++){
				shift_in(\@turn_off, $digit, $ncas, 100, 0);
				if($ni ==($ncas-1)){
					load();
				}#end if
			}#end for
		}#end outer for
	}#end if multiple 7219's
	else{#just one
		#@all_digits is an array of array references
		#representing each digit
		foreach my $digit (@all_digits){
			shift_in(\@turn_off, $digit, 1, 100, 1);
		}#end foreach
	}#end else
}#end all_off

sub disp_teston{
	#turn on display test, 
	#all digits and dp
	shift_in(\@turn_on, \@disptstreg, 1, 200, 1);
}#end disp_test

sub disp_testoff{
	#turn off display test, 
	#all digits and dp
	shift_in(\@turn_off, \@disptstreg, 1, 200, 1);
}#end disp_test

#################### EFFECTS######################
sub clockwise_circles{
	#number of iterations
	my $i = $_[0];
	my $k = 0;
	
	while($k<$i){
		for(my $n=0;$n<6;$n++){
			#outer for, each segment
			for(my $x=0; $x<8; $x++){
				#inner for, each digit
				shift_in(@all_segs[$n], @all_digits[$x], 1, 100, 1);
			}#end inner for
		}#end outer for

		$k++;

	}#end while
	all_off();
}#end clockwise_circles

sub countercw_circles{
	#number of iterations
	my $i = $_[0];
	my $k = 0;
	my @revall_segs = reverse @all_segs;
	while($k<$i){
		for(my $n=1;$n<7;$n++){
			#outer for, each segment
			for(my $x=0; $x<8; $x++){
				#inner for, each digit
				shift_in(@revall_segs[$n], @all_digits[$x], 1, 100, 1);
			}#end inner for
		}#end outer for

		$k++;

	}#end while
	all_off();
}#end countercw_circles

sub bullets_lrtop {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rltop {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_lrmid {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rlmid {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_lrbot {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rlbot {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop
1;

Here is an example of how simple it is to use one of these LED displays using this driver.

 

#!/usr/bin/perl
use strict;
require 'MAX7219.pl';        #required driver for LED array

init7219(0,1,2);             #set the GPIO lines on the pine64 for clock, data in, and latch
turn_on(1);                  #turn on only 1 MAX7219 chip
set_scanlimit(1);            #tell it to use all 8 digits for 1 LED array
set_intensity('max');        #maximum intensity

print_sentence("pine64 rules the world", 500000);

pine64 unboxing

I love tinkering with single board computers.  I was an early adopter of the CHIP  (until they tanked) and later the raspberry pi family of SBC’s.

chip single board computer
the now defunct CHIP

I was looking for a cheaper alternative to the pi model 3 ($35) yet more powerful than the pi zero w.  There were several alternatives such as the orangePi, but after reading the forums, it looked like there were some serious overheating problems and poor support from the community.  I came across the pine64 and just had to get one.

I haven’t done much with it yet; just burned debian + mate desktop that a member of the community made available, but so far I am impressed.  I got the base Allwinner quad-core 64-bit ,512MB  memory model for just $15!

pine64 processor info

Somewhat larger than the pi 3, it has 10/100 ethernet, HDMI, 2x USB ports, and lots of GPIO pins.   I can’t wait to get started.

pine64 vs raspberry pi 3b
pine64 vs raspberry pi 3b