File Coverage

blib/lib/PINE64/MAX7219.pm
Criterion Covered Total %
statement 9 195 4.6
branch 0 42 0.0
condition 0 6 0.0
subroutine 3 23 13.0
pod 18 20 90.0
total 30 286 10.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2 1     1   68301 use strict;
  1         2  
  1         33  
3 1     1   510 use PINE64::GPIO;
  1         548  
  1         33  
4 1     1   544 use Time::HiRes qw(usleep);
  1         1438  
  1         6  
5              
6             package PINE64::MAX7219;
7              
8             our $VERSION = '0.91';
9              
10             #routines to control a MAX7219 8-digit LED
11             #display driver.
12             #This is implemented as bit-banged SPI.
13             #data is shifted into regs on rising edge of CLK,
14             #output displayed on rising edge of LOAD
15              
16             #NOTES:
17             #This version supports cascading several 7219's. I have
18             #noticed that even with programs that assume only one
19             #7219, that if there is another cascaded, the exact same
20             #output is mirrored on subsequent displays.
21              
22             #I assume that turn_on, set_intenisty, set_scanlimit, etc
23             #all get shifted into the next chip anyway, however,
24             #these subroutines take the number of cascaded 7219s as
25             #an argument and shift in and latch the necessary
26             #controls to use the display.
27              
28              
29             #global vars for gpio init function
30             #$clk SPI clock
31             #$ds SPI data
32             #$load SPI chip select
33             my ($clk, $ds, $load);
34              
35             ############### GLOBAL VARS #####################
36             #shutdown register
37             my @sdreg = (0,0,0,0,1,1,0,0);
38             my @turn_on = (0,0,0,0,0,0,0,1);
39             my @turn_off = (0,0,0,0,0,0,0,0);
40              
41             #set scan limit register
42             my @slimreg = (0,0,0,0,1,0,1,1);
43             #last 3 LSBs scan all 8 digits
44             my @slregall = (0,0,0,0,0,1,1,1);
45              
46             #display test register
47             my @disptstreg = (0,0,0,0,1,1,1,1);
48              
49             #intensity register
50             my @intreg = (0,0,0,0,1,0,1,0);
51              
52             #digit addrs
53             #first 4 bits D15-D12
54             #don't care bits, then addr
55             my @_d0 = (0,1,0,1,0,0,0,1);
56             my @_d1 = (0,1,0,1,0,0,1,0);
57             my @_d2 = (0,1,0,1,0,0,1,1);
58             my @_d3 = (0,1,0,1,0,1,0,0);
59             my @_d4 = (0,1,0,1,0,1,0,1);
60             my @_d5 = (0,1,0,1,0,1,1,0);
61             my @_d6 = (0,1,0,1,0,1,1,1);
62             my @_d7 = (0,1,0,1,1,0,0,0);
63              
64             #0-9
65             my @_1 = (0,0,1,1,0,0,0,0);
66             my @_2 = (0,1,1,0,1,1,0,1);
67             my @_3 = (0,1,1,1,1,0,0,1);
68             my @_4 = (0,0,1,1,0,0,1,1);
69             my @_5 = (0,1,0,1,1,0,1,1);
70             my @_6 = (0,1,0,1,1,1,1,1);
71             my @_7 = (0,1,1,1,0,0,0,0);
72             my @_8 = (0,1,1,1,1,1,1,1);
73             my @_9 = (0,1,1,1,0,0,1,1);
74             my @_0 = (0,1,1,1,1,1,1,0);
75             my @_all = (1,1,1,1,1,1,1,1);
76              
77             #alpha chars
78             my @_a = (0,1,1,1,0,1,1,1);
79             my @_b = (0,0,0,1,1,1,1,1);
80             my @_c = (0,1,0,0,1,1,1,0);
81             my @_d = (0,0,1,1,1,1,0,1);
82             my @_e = (0,1,0,0,1,1,1,1);
83             my @_f = (0,1,0,0,0,1,1,1);
84             my @_g = (0,1,1,1,1,0,1,1);
85             my @_h = (0,0,1,1,0,1,1,1);
86             my @_i = (0,0,0,0,0,1,0,0);
87             my @_j = (0,0,1,1,1,1,0,0);
88             my @_k = (0,0,1,1,0,1,1,0);
89             my @_l = (0,0,0,0,1,1,1,0);
90             my @_m = (1,0,0,1,0,1,0,0);
91             my @_n = (0,0,0,1,0,1,0,1);
92             my @_o = (0,0,0,1,1,1,0,1);
93             my @_p = (0,1,1,0,0,1,1,1);
94             my @_q = (0,1,1,1,0,0,1,1);
95             my @_r = (0,0,0,0,0,1,0,1);
96             my @_s = (0,1,0,1,1,0,1,1);
97             my @_t = (0,0,0,0,1,1,1,1);
98             my @_u = (0,0,0,1,1,1,0,0);
99             my @_v = (0,0,0,1,1,1,0,0);
100             my @_w = (0,0,0,1,0,1,0,0);
101             my @_x = (0,0,0,1,1,1,0,1);
102             my @_y = (0,0,1,1,1,0,1,1);
103             my @_z = (0,1,1,0,1,1,0,1);
104              
105             #special chars
106             my @_dp = (1,0,0,0,0,0,0,0);
107             my @_sp = (0,0,0,0,0,0,0,0);
108             my @_dash = (0,0,0,0,0,0,0,1);
109             my @_comma = (1,0,0,0,0,0,0,0);
110             my @_period = (1,0,0,0,0,0,0,0);
111             my @_question = (1,1,1,0,0,0,1,0);
112             my @_exclaimation = (1,0,1,1,0,0,0,0);
113              
114             #individual segments
115             my @_seg_a = (0,1,0,0,0,0,0,0);
116             my @_seg_b = (0,0,1,0,0,0,0,0);
117             my @_seg_c = (0,0,0,1,0,0,0,0);
118             my @_seg_d = (0,0,0,0,1,0,0,0);
119             my @_seg_e = (0,0,0,0,0,1,0,0);
120             my @_seg_f = (0,0,0,0,0,0,1,0);
121             my @_seg_g = (0,0,0,0,0,0,0,1);
122              
123             #@all_digits is an arra of array references
124             #representing each digit used to operate
125             #on each digit
126             my @all_digits = (\@_d0,\@_d1,\@_d2,\@_d3,\@_d4,\@_d5,\@_d6,\@_d7);
127              
128             #@all_segs is an array of array references
129             #representing each segment of a 7-seg LED array
130             my @all_segs = (\@_seg_a,\@_seg_b,\@_seg_c,\@_seg_d,\@_seg_e,\@_seg_f,\@_seg_g);
131              
132             #Hash that maps alphanumeric chars to array ref
133             my %alphanums = (
134             'A' => \@_a,
135             'B' => \@_b,
136             'C' => \@_c,
137             'D' => \@_d,
138             'E' => \@_e,
139             'F' => \@_f,
140             'G' => \@_g,
141             'H' => \@_h,
142             'I' => \@_i,
143             'J' => \@_j,
144             'K' => \@_k,
145             'L' => \@_l,
146             'M' => \@_m,
147             'N' => \@_n,
148             'O' => \@_o,
149             'P' => \@_p,
150             'Q' => \@_q,
151             'R' => \@_r,
152             'S' => \@_s,
153             'T' => \@_t,
154             'U' => \@_u,
155             'V' => \@_v,
156             'W' => \@_w,
157             'X' => \@_x,
158             'Y' => \@_y,
159             'Z' => \@_z,
160             '0', => \@_0,
161             '1', => \@_1,
162             '2', => \@_2,
163             '3', => \@_3,
164             '4', => \@_4,
165             '5', => \@_5,
166             '6', => \@_6,
167             '7', => \@_7,
168             '8', => \@_8,
169             '9', => \@_9,
170             '.' => \@_period,
171             '-' => \@_dash,
172             ',' => \@_comma,
173             '?' => \@_question,
174             '!' => \@_exclaimation
175             );#end %alphanums declaration
176              
177             #instantiate PINE64 gpio device
178             my $p64 = PINE64::GPIO->new();
179              
180             ############### SUBROUTINES #####################
181             sub new{
182 0     0 1   my $class = shift;
183 0           my $self = bless {}, $class;
184              
185             #args are the GPIO pin numbers from PINE64::GPIO
186             #that will be used for bit-bang SPI
187              
188             #initializes gpio lines and set to low
189 0           $clk = $_[0];
190 0           $ds = $_[1];
191 0           $load = $_[2];
192              
193 0           $p64->gpio_enable($clk, 'out');
194 0           $p64->gpio_write($clk, 0);
195 0           $p64->gpio_enable($ds, 'out');
196 0           $p64->gpio_write($ds, 0);
197 0           $p64->gpio_enable($load, 'out');
198 0           $p64->gpio_write($load, 0);
199              
200 0           return $self;
201             }#end new
202              
203             sub shift_in{
204             #data is shifted in with 16 bit packets: 8-bit segment,
205             #4-bit digit address, 4-bit dont care bits
206              
207             #used by internal methods only
208              
209             #array ref seg data
210 0     0 1   my $leds = $_[0];
211             #array ref seg addr
212 0           my $addr = $_[1];
213             #number cascaded 7219s
214 0           my $ncas = $_[2];
215             #delay in milliseconds
216 0           my $delay = $_[3];
217             #latch flag
218 0           my $lf = $_[4];
219              
220             #high flag for data gpio line
221 0           my $hf = 0;
222              
223             #my $ncp = $ncas * 32; #min number 32 for 16 clock pulses
224 0           my $ncp = 32;
225             ##print "ncp: $ncp\n";
226              
227             #main clock loop
228 0           my $i=0;
229             #$state toggles from 1 to 0 needed for clock pulse
230 0           my $state = 0;
231             #ensures first pulse goes from low to high
232 0           my $seed = 3;
233              
234 0           while($i<$ncp){#correct num clock pulses
235 0           $state = $seed%2;
236 0           $seed++;
237            
238             #load data in last 8 clock pulses
239             #make DS high before clock pulse; only on even num
240             #so index array is whole number
241             #MSB read first, then addr, last data
242 0 0         if(($i%2) eq 0){
243              
244             #address D8-D11, and don't care bits D12-D15
245 0 0 0       if($addr->[$i/2] == 1 && $i<=14){
246 0           $p64->gpio_write($ds,1);
247 0           $hf = 1;#set high flag
248             }#end addr high bit
249            
250             #7-seg data, D0-D7
251 0 0 0       if($leds->[($i-16)/2] == 1 && $i > 14){#array ref, light this led up
252             #test; set q1 high
253             ##print "inside data loop, i: $i\tindex: " .(($i-16)/2) . "\n";
254 0           $p64->gpio_write($ds, 1);
255 0           $hf = 1;#set high flag
256             }#end if build D0-D7
257              
258             }#end if even $i
259              
260             #TEST
261             #print "i:\t$i\nD:\t$hf\n\n";
262            
263             #toggle clock pulse
264 0           $p64->gpio_write($clk, $state);
265 0           Time::HiRes::usleep($delay);#sleep .001 sec
266              
267             #lower data if high flag set
268 0 0         if($hf eq 1){
269 0           $p64->gpio_write($ds,0);
270 0           $hf = 0;#reset high flag
271             }#end if
272              
273 0           $i++;
274             }#end while
275              
276             #latch output lines
277 0 0         if($lf eq 1){
278 0           load();
279             }#end latch flag
280              
281 0           $p64->gpio_write($clk,0);#set clock pulse low
282              
283             }#end shift_in
284              
285             sub load{
286             #toggles LOAD (SPI chip select) pin on 7219 to send to output pins. When the pin goes from low to high, sets output.
287              
288             #enable XIO-p2
289 0     0 1   $p64->gpio_enable($load, 'out');
290              
291             #ensure it is low
292 0           $p64->gpio_write($load, 0);#low
293              
294             #go from low to high
295 0           $p64->gpio_write($load, 1);#high
296             #print "LATCH HIGH\n";
297 0           Time::HiRes::usleep(500);#pause 0.0005 second
298              
299             #reset to low
300 0           $p64->gpio_write($load, 0);#low
301             #print "LATCH LOW\n";
302            
303             }#end load
304              
305             sub print_sentence{
306             #currently for a single 7219 array
307 0     0 1   my $sentence = $_[1];
308             #set to all uppercase letters
309 0           $sentence = uc $sentence;
310              
311             #delay between words in microseconds
312 0           my $delay = $_[2];
313              
314             #all off flag, set if reading, unset if
315             #you want the text to remain on the array
316             #if empty, clears the text
317 0           my $cleartxt_flag = $_[3];
318              
319 0           my @words = split / /, $sentence;
320 0           my $numwords = @words;
321            
322             #loop through words
323 0           for(my $i=0;$i<$numwords;$i++){
324             #split word into an array of chars
325 0           my @letters = split //, $words[$i];
326              
327             #number of chars in word
328 0           my $numalphanums = @letters;
329             #print "num letters: $numalphanums\n";
330            
331             #first letter
332 0           my $ln = 0;
333            
334             #reverse @all_digits to display words
335             #left to right
336 0           my @rev_segs = reverse @all_digits;
337              
338 0           foreach my $digit (@rev_segs){
339            
340             #limited to 8 digits for now
341 0           shift_in($alphanums{$letters[$ln]}, $digit, 1, 250, 1 );
342             #print "letter: $letters[$ln]\n";
343 0           $ln++;#go to next letter
344             }#end letters inner for loop
345            
346 0           Time::HiRes::usleep($delay);
347 0 0         unless($cleartxt_flag == 1){
348 0           all_off();
349             }#end if
350             }#end for numwords
351             }#end print_sentence
352              
353             sub print_interleaved{
354             #takes separate strings for each
355             #line of displays. The strings are
356             #assumed to fit into the 8-digit
357             #line of an led array for a 7219
358 0     0 1   my $str1 = $_[1];
359 0           my $str2 = $_[2];
360              
361             #upper case
362 0           $str1 = uc $str1;
363 0           $str2 = uc $str2;
364              
365             #convert strings to array of chars
366 0           my @s1 = split //, $str1;
367 0           my @s2 = split //, $str2;
368              
369             #pad string arrays with spaces
370             #if less than 8 chars
371 0           my $s1_len = @s1;
372 0           my $s2_len = @s2;
373             #print "s1len: $s1_len\ts2len: $s2_len\n";
374              
375 0 0         if($s1_len < 8){
376 0           my $nsp = 8-$s1_len;
377 0           for(my $n=$s1_len;$n<8;$n++){
378 0           $s1[$n] = " ";
379             }#end for pad w/ spaces
380             }#end pad str1 with spaces
381              
382 0 0         if($s2_len < 8){
383 0           my $nsp = 8-$s2_len;
384 0           for(my $n=$s2_len;$n<8;$n++){
385 0           $s2[$n] = " ";
386             }#end for pad w/ spaces
387             }#end pad str1 with spaces
388              
389 0           $s1_len = @s1;
390 0           $s2_len = @s2;
391             #print "s1len: $s1_len\ts2len: $s2_len\n";
392              
393             #reverse @all_digits to display words
394             #left to right
395 0           my @rev_segs = reverse @all_digits;
396              
397             #init letter number to 0
398 0           my $ln = 0;
399              
400 0           foreach my $digit (@rev_segs){
401 0           shift_in($alphanums{$s2[$ln]}, $digit, 1, 250, 0 );
402 0           shift_in($alphanums{$s1[$ln]}, $digit, 1, 250, 0 );
403 0           load();
404 0           $ln++;
405             }#end for
406             }#end print_interleaved
407              
408             sub turn_on{
409             #ncas is number of cascaded MAX7219 displays
410 0     0 1   my $ncas = $_[1];
411 0 0         if(defined($ncas)){
412             #print "tu ncas defined\n";
413 0           for(my $ni=0;$ni<$ncas;$ni++){
414 0           shift_in(\@turn_on, \@sdreg, $ncas, 250, 0);
415 0 0         if($ni ==($ncas-1)){
416             #print "tu load\n";
417 0           load();
418             }#end if
419             }#end for
420             }#end if multiple 7219's
421             else{#just one
422             #set shutdown register to normal operation
423 0           shift_in(\@turn_on, \@sdreg, 1, 250, 1);
424             }#end else
425             }#end turn_on
426              
427             sub turn_off{
428             #ncas is number of cascaded MAX7219 displays
429 0     0 0   my $ncas = $_[1];
430 0 0         if(defined($ncas)){
431 0           for(my $ni=0;$ni<$ncas;$ni++){
432 0           shift_in(\@turn_off, \@sdreg, $ncas, 250, 0);
433 0 0         if($ni ==($ncas-1)){
434 0           load();
435             }#end if
436             }#end for
437             }#end if multiple 7219's
438             else{#just one
439             #set shutdown register to off
440 0           shift_in(\@turn_off, \@sdreg, 1, 250, 1);
441             }#end else
442             }#end turn_off
443              
444             sub set_scanlimit{
445             #ncas is number of cascaded MAX7219 displays
446 0     0 1   my $ncas = $_[1];
447 0 0         if(defined($ncas)){
448             #print "sl ncas defined\n";
449 0           for(my $ni=0;$ni<$ncas;$ni++){
450 0           shift_in(\@slregall, \@slimreg, $ncas, 250, 0);
451 0 0         if($ni ==($ncas-1)){
452 0           load();
453             #print "sl load\n";
454             }#end if
455             }#end for
456             }#end if multiple 7219's
457             else{#just one
458             #set scan limit register
459 0           shift_in(\@slregall, \@slimreg, 1, 500, 1);
460             #print "sl 1 chip\n";
461             }#end else
462             }#end set_scanlimit
463              
464             sub set_intensity{
465             #takes string as arg: min, dim, mid, bright, max
466 0     0 1   my $intensity = $_[1];
467            
468             #default to max
469 0           my @intregdata = (0,0,0,0,1,1,1,1);
470 0 0         if($intensity eq 'min'){
471 0           @intregdata = (0,0,0,0,0,0,0,0);
472             }#end if
473 0 0         if($intensity eq 'dim'){
474 0           @intregdata = (0,0,0,0,0,0,1,1);
475             }#end if
476 0 0         if($intensity eq 'mid'){
477 0           @intregdata = (0,0,0,0,0,1,1,1);
478             }#end if
479 0 0         if($intensity eq 'bright'){
480 0           @intregdata = (0,0,0,0,1,0,1,1);
481             }#end if
482 0 0         if($intensity eq 'max'){
483 0           @intregdata = (0,0,0,0,1,1,1,1);
484             }#end if
485              
486             #print "Intensity: $intensity\n";
487 0           shift_in(\@intregdata, \@intreg, 1, 250, 1);
488             }#end set_intensity
489              
490             sub all_off{
491             #clear display
492             #call after turned on, and
493             #scan reg set to all digits
494 0     0 1   my $ncas = $_[1];
495 0 0         if(defined($ncas)){
496 0           foreach my $digit(@all_digits){
497 0           for(my $ni=0;$ni<$ncas;$ni++){
498 0           shift_in(\@turn_off, $digit, $ncas, 100, 0);
499 0 0         if($ni ==($ncas-1)){
500 0           load();
501             }#end if
502             }#end for
503             }#end outer for
504             }#end if multiple 7219's
505             else{#just one
506             #@all_digits is an array of array references
507             #representing each digit
508 0           foreach my $digit (@all_digits){
509 0           shift_in(\@turn_off, $digit, 1, 100, 1);
510             }#end foreach
511             }#end else
512             }#end all_off
513              
514             sub disp_teston{
515             #turn on display test,
516             #all digits and dp
517 0     0 1   shift_in(\@turn_on, \@disptstreg, 1, 200, 1);
518             }#end disp_test
519              
520             sub disp_testoff{
521             #turn off display test,
522             #all digits and dp
523 0     0 1   shift_in(\@turn_off, \@disptstreg, 1, 200, 1);
524             }#end disp_test
525              
526             #################### EFFECTS######################
527             sub clockwise_circles{
528             #number of iterations
529 0     0 1   my $i = $_[1];
530 0           my $k = 0;
531            
532 0           while($k<$i){
533 0           for(my $n=0;$n<6;$n++){
534             #outer for, each segment
535 0           for(my $x=0; $x<8; $x++){
536             #inner for, each digit
537 0           shift_in(@all_segs[$n], @all_digits[$x], 1, 100, 1);
538             }#end inner for
539             }#end outer for
540              
541 0           $k++;
542              
543             }#end while
544 0           all_off();
545             }#end clockwise_circles
546              
547             sub countercw_circles{
548             #number of iterations
549 0     0 0   my $i = $_[1];
550 0           my $k = 0;
551 0           my @revall_segs = reverse @all_segs;
552 0           while($k<$i){
553 0           for(my $n=1;$n<7;$n++){
554             #outer for, each segment
555 0           for(my $x=0; $x<8; $x++){
556             #inner for, each digit
557 0           shift_in(@revall_segs[$n], @all_digits[$x], 1, 100, 1);
558             }#end inner for
559             }#end outer for
560              
561 0           $k++;
562              
563             }#end while
564 0           all_off();
565             }#end countercw_circles
566              
567             sub bullets_lrtop {
568             #number of iterations
569 0     0 1   my $ni = $_[1];
570 0           for(my $n=0;$n<$ni;$n++){
571 0           for(my $i=8;$i>=0;$i--){
572 0           shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
573 0           all_off();
574             }#end inner for
575             }#end outer for
576 0           all_off();
577             }#end bullets_lrtop
578              
579             sub bullets_rltop {
580             #number of iterations
581 0     0 1   my $ni = $_[1];
582 0           for(my $n=0;$n<$ni;$n++){
583 0           for(my $i=0;$i<=8;$i++){
584 0           shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
585 0           all_off();
586             }#end inner for
587             }#end outer for
588 0           all_off();
589             }#end bullets_lrtop
590              
591             sub bullets_lrmid {
592             #number of iterations
593 0     0 1   my $ni = $_[1];
594 0           for(my $n=0;$n<$ni;$n++){
595 0           for(my $i=8;$i>=0;$i--){
596 0           shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
597 0           all_off();
598             }#end inner for
599             }#end outer for
600 0           all_off();
601             }#end bullets_lrtop
602              
603             sub bullets_rlmid {
604             #number of iterations
605 0     0 1   my $ni = $_[1];
606 0           for(my $n=0;$n<$ni;$n++){
607 0           for(my $i=0;$i<=8;$i++){
608 0           shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
609 0           all_off();
610             }#end inner for
611             }#end outer for
612 0           all_off();
613             }#end bullets_lrtop
614              
615             sub bullets_lrbot {
616             #number of iterations
617 0     0 1   my $ni = $_[1];
618 0           for(my $n=0;$n<$ni;$n++){
619 0           for(my $i=8;$i>=0;$i--){
620 0           shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
621 0           all_off();
622             }#end inner for
623             }#end outer for
624 0           all_off();
625             }#end bullets_lrtop
626              
627             sub bullets_rlbot {
628             #number of iterations
629 0     0 1   my $ni = $_[1];
630 0           for(my $n=0;$n<$ni;$n++){
631 0           for(my $i=0;$i<=8;$i++){
632 0           shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
633 0           all_off();
634             }#end inner for
635             }#end outer for
636 0           all_off();
637             }#end bullets_lrtop
638             1;
639              
640             __END__