Larson Scanner

Not Battlestar Galactica.

Most people associate a Larson scanner with Battlestar Galactica (1978) and Knight Rider (1982) but there was an underrated sci-fi film in 1979 called The Black Hole, with a robot sidekick henchman called Maximilian.

ScrewedSculpts asked if I could create this iconic effect for a version of Maximilian that he was building.

The outcome and Arduino sketch is below.

Chip Choices

Arduino Mini or Arduino Nano ?

Yes these are clones, as the end product is going to live permanently in a robot shell with no future access.

They will live there forever and will never be reused.

First Prototype

The original plan was to have larger LEDs.

These 10mm clear LEDs are crazy bright but the overall form factor could be too wide.

LED Comparison

The 10mm LEDs would have a greater impact and are brighter overall but would take up too much horizontal space.

A Slight Redesign

So at this point it was decided to add in motion detection functionality.

The plan being that when movement is detected it will trigger the scanner effect.

Time to add a PIR sensor into the mix.

I have zero CAD skills but I do own a pen.

A Working PIR

Using a pair of breadboards I can now trigger the scanner effect.

This particular PIR detector is (I think) the HC-SR505 with an effective range of 3m.

LEDs in Place

Now that the proof of concept is all working, time to dig out the toolbox.

LEDs on Veroboard first.

LEDs and Resistors in Place

Closely followed by adding all the resistors.

(The underneath of the board has already had the appropriate tracks cut.)

All Wired Up

And then start to create a wiring loom.

Testing The Arduino

The LED board has been wired up (and liberal amounts of hot glue to ensure that none of the wires break free.

Testing to see that all of the LEDs still work.

Testing The Arduino and PIR

With a working LED board and a known working Arduino it’s time to add in the PIR module.

There are two sets of buttons now:

  • A power button that when on, the scanner effect will run
  • A sensor button that then on will only trigger the scanner effect when activated (asssuming the power is on

Final Board and All Wrapped Up

Finally all working and wrapped up in cable tidy wrap.

This site won’t allow the uploading of videos but there is a facebook video of the finished piece here:

The Arduino Sketch:

  • The scanner code came from here
  • The PIR code attribution is in the comments

Both of the above work fine in isolation, any errors after that are all mine.

I hope this sketch helps someone looking to do something similar.

Larson Scanner sketch with PIR activation.
// Larson array
//
// PORTDB_larsen2ways
// 
// goes both ways: Back AND Forth  :)

// 
//                             11
//                             1098765432    
const word dimbits[] = { 0b0000000011111100, // frame 1
                         0b0000000011111000,
                         0b0000000011110000, 
                         0b0000000011100000, 
                         0b0000000011000000, 
                         0b0000000010000000, 
                         0b0000000010000000, 
                         0b0000000010000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000000111111000, // frame 2
                         0b0000000111110000,
                         0b0000000111100000, 
                         0b0000000111000000, 
                         0b0000000110000000, 
                         0b0000000100000000, 
                         0b0000000100000000, 
                         0b0000000100000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000001111110000, // frame 3
                         0b0000001111100000,
                         0b0000001111000000, 
                         0b0000001110000000, 
                         0b0000001100000000, 
                         0b0000001000000000, 
                         0b0000001000000000, 
                         0b0000001000000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000011111100000, // frame 4
                         0b0000011111000000,
                         0b0000011110000000, 
                         0b0000011100000000, 
                         0b0000011000000000, 
                         0b0000010000000000, 
                         0b0000010000000000, 
                         0b0000010000000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000111111000000, // frame 5 ****
                         0b0000111110000000,
                         0b0000111100000000, 
                         0b0000111000000000, 
                         0b0000110000000000, 
                         0b0000100000000000, 
                         0b0000100000000000, 
                         0b0000100000000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000111110000000, // frame 6
                         0b0000111100000000,
                         0b0000111000000000, 
                         0b0000110000000000, 
                         0b0000110000000000, 
                         0b0000010000000000, 
                         0b0000010000000000, 
                         0b0000010000000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000111100000000, // frame 7
                         0b0000111000000000,
                         0b0000111000000000, 
                         0b0000111000000000, 
                         0b0000011000000000, 
                         0b0000001000000000, 
                         0b0000001000000000, 
                         0b0000001000000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000111100000000, // frame 8
                         0b0000111100000000,
                         0b0000111100000000, 
                         0b0000011100000000, 
                         0b0000001100000000, 
                         0b0000000100000000, 
                         0b0000000100000000, 
                         0b0000000100000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000111110000000, // frame 9
                         0b0000111110000000,
                         0b0000011110000000, 
                         0b0000001110000000, 
                         0b0000000110000000, 
                         0b0000000010000000, 
                         0b0000000010000000, 
                         0b0000000010000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000111111000000, // frame 10
                         0b0000011111000000,
                         0b0000001111000000, 
                         0b0000000111000000, 
                         0b0000000011000000, 
                         0b0000000001000000, 
                         0b0000000001000000, 
                         0b0000000001000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                                 
                         0b0000011111100000, // frame 11
                         0b0000001111100000,
                         0b0000000111100000, 
                         0b0000000011100000, 
                         0b0000000001100000, 
                         0b0000000000100000, 
                         0b0000000000100000, 
                         0b0000000000100000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                              
                         0b0000001111110000, // frame 12
                         0b0000000111110000,
                         0b0000000011110000, 
                         0b0000000001110000, 
                         0b0000000000110000, 
                         0b0000000000010000, 
                         0b0000000000010000, 
                         0b0000000000010000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                              
                         0b0000000111111000, // frame 13
                         0b0000000011111000,
                         0b0000000001111000, 
                         0b0000000000111000, 
                         0b0000000000011000, 
                         0b0000000000001000, 
                         0b0000000000001000, 
                         0b0000000000001000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                              
                         0b0000000011111100, // frame 14 ***
                         0b0000000001111100,
                         0b0000000000111100, 
                         0b0000000000011100, 
                         0b0000000000001100, 
                         0b0000000000000100, 
                         0b0000000000000100, 
                         0b0000000000000100, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000, 
//                             1098765432                              
                         0b0000000001111100, // frame 15
                         0b0000000000111100,
                         0b0000000000011100, 
                         0b0000000000001100, 
                         0b0000000000001100, 
                         0b0000000000001000, 
                         0b0000000000001000, 
                         0b0000000000001000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000, 
//                             1098765432                              
                         0b0000000000111100, // frame 16
                         0b0000000000011100,
                         0b0000000000011100, 
                         0b0000000000011100, 
                         0b0000000000011000, 
                         0b0000000000010000, 
                         0b0000000000010000, 
                         0b0000000000010000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                              
                         0b0000000000111100, // frame 17
                         0b0000000000111100,
                         0b0000000000111100, 
                         0b0000000000111000, 
                         0b0000000000110000, 
                         0b0000000000100000, 
                         0b0000000000100000, 
                         0b0000000000100000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
//                             1098765432                              
                         0b0000000001111100, // frame 18
                         0b0000000001111100,
                         0b0000000001111000, 
                         0b0000000001110000, 
                         0b0000000001100000, 
                         0b0000000001000000, 
                         0b0000000001000000, 
                         0b0000000001000000, 
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000,
                         0b0000000000000000};                         
                         
unsigned long duration = 0;
unsigned long times_out = 7500;  // frame's time out "speed"
                                 // 500 for way fast
                                 // 50000 for way slow
                        
int idx;
byte framepointer = 0;

/* 
 * //////////////////////////////////////
 * //making sense of the sensor's output
 * //////////////////////////////////////
 *
 * Switches a LED according to the state of the sensors output pin.
 * Determines the beginning and end of continuous motion sequences.
 *
 * @author: Kristian Gohlke / krigoo (_) gmail (_) com / https://krx.at
 * @date:   3. September 2006 
 *
 * kr1 (cleft) 2006 
 * released under a creative commons "Attribution-NonCommercial-ShareAlike 2.0" license
 * https://creativecommons.org/licenses/by-nc-sa/2.0/de/
 *
 * The sensor's output pin goes to HIGH if motion is present.
 * However, even if motion is present it goes to LOW from time to time, 
 * which might give the impression no motion is present. 
 * This program deals with this issue by ignoring LOW-phases shorter than a given time, 
 * assuming continuous motion is present during these phases.
 *  
 */

/////////////////////////////
//VARS
//the time we give the sensor to calibrate (10-60 secs according to the datasheet)
//different sensor so smaller calibration time
int calibrationTime = 5;        

//the time when the sensor outputs a low impulse
long unsigned int lowIn;        

//the amount of milliseconds the sensor has to be low 
//before we assume all motion has stopped
long unsigned int pause = 5000;  

boolean lockLow = true;
boolean takeLowTime;  

int radPin = 12;    //the digital pin connected to the sensor's output
//int ledPin = 13;

/////////////////////////////
//SETUP
void setup(){

//Larson
  //pinMode(13,OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  //PORTD &= 0b00000011;

  Serial.begin(9600);
  pinMode(radPin, INPUT);
  //pinMode(ledPin, OUTPUT);
  digitalWrite(radPin, LOW);

  //give the sensor some time to calibrate
  Serial.print("calibrating sensor ");
    for(int i = 0; i < calibrationTime; i++){
      Serial.print(".");
      delay(1000);
      }
    Serial.println(" done");
    Serial.println("SENSOR ACTIVE");
    delay(50);
  }

////////////////////////////
//LOOP
void loop(){

     if(digitalRead(radPin) == HIGH){
       //digitalWrite(ledPin, HIGH);   //the led visualizes the sensors output pin state
       if(lockLow){  
         //makes sure we wait for a transition to LOW before any further output is made:
         lockLow = false;            
         Serial.println("---");
         Serial.print("motion detected at ");
         Serial.print(millis()/1000);
         Serial.println(" sec"); 
         delay(50);
         }

// Larson code
  for (framepointer = 0; framepointer < 216; framepointer = (framepointer + 12))
  // 0-11,12-33,24-35,36-47,48-59,60-71,72-83,84-95,96-107,
  // 108-119,120-131,132-143,144-155,156-167,168-179,
  // 180-191,192-203,204-215,216
  {
    for(duration=0; duration<times_out; duration++)
    // times_out is the number of
    // frame repetitions
    {
      for(idx = framepointer; idx < (framepointer + 12); idx++)
      {
        commitPORTs();
      }
    }
  }
  framepointer = 0;
  //delay(500);
             
         takeLowTime = true;
       }

     if(digitalRead(radPin) == LOW){       
       //digitalWrite(ledPin, LOW);  //the led visualizes the sensors output pin state

       if(takeLowTime){
        lowIn = millis();          //save the time of the transition from high to LOW
        takeLowTime = false;       //make sure this is only done at the start of a LOW phase
        }
       //if the sensor is low for more than the given pause, 
       //we assume that no more motion is going to happen
       if(!lockLow && millis() - lowIn > pause){  
           //makes sure this block of code is only executed again after 
           //a new motion sequence has been detected
           lockLow = true;                        
           Serial.print("motion ended at ");      //output
           Serial.print((millis() - pause)/1000);
           Serial.println(" sec");
           delay(50);
           }
       }
  }

// Larson array
void commitPORTs ()
{
  PORTD = dimbits[idx];
  PORTB = dimbits[idx]/256;
}
1 Like

Nice, that looks pretty darn cool. =)

Hi Kerry,

Ta, I don’t do a lot of Veroboard work these days so it was nice and quite satisfying to go back to it.

Cheers,

Simon.

Nothin wrong with old school. I use a lot of Proto Hats and proto boards in my projects.

Did some poking around and see that the Blinkt and LED Shim both have Larson example code for Python. Might give that a try with my just sitting there doing nothing Blinkt. I have two LED shims but they are in use.
The first thing that came to mind when I read your first post was,
By Your Command lol. showing my age, =)