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 but for a version of Maximilian that he was building.

First things first – chip choices

The choice is between an Arduino Mini and an Arduino Nano.

Both would work and it’s a question of how to power the finished unit.

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

An Arduino Mini next to and Arduino Nano. Choosing which to use is the start of the Larson scanner build. Arduino Mini vs Arduino Nano

Larson Scanner prototype

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

An Arduino Mini on a breadboard with LEDs wired up. This id the first working prototype of the Larson scanner. Arduino Mini Larson Breadboard

LED comparison

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

Large clear LEDs next to smaller red LEDs. I need to decide which LEDs to use in the Larson scanner. LED Comparison

A slight redesign

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

And then add a PIR sensor into the mix and make it responsive to nearby movement.

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

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

A drawing showing how the LED board will connect to the Arduino board. It is crudely done. Who Needs EAGLE?

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.

Two breadboards, the top one has all of the LEDs slotted in and they are lit up red. The bottom one has the Arduino and a PIR motion detector. Wires connect the two boards. Added PIR Sensor

Larson Scanner LEDs in place

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

Workbench showing LEDs and resistors being put into VeroBoard. Everything is in pieces. VeroBoard With LEDs

Larson Scanner LEDs and resistors in place

And then closely followed by adding all the resistors.

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

Workbench showing LEDs and resistors being put into VeroBoard. Everything is now assembled. VeroBoard With LEDs And Resistors

Finally, all wired up

And then start to create a wiring loom.

The completed LED board with resistors and wire in place. LED Board Wired

Testing the Arduino

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

Testing to see that all of the LEDs still work.

The completed LED board with resistors and wire in place connected to a breadboard and being tested using the Arduino. LED Board Test Rig

Testing the Arduino and PIR

Then, 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 (assuming the power is on

The completed Larson scanner LED board with resistors and wire in place connected to a breadboard and being tested using the Arduino and the PIR motion detector. LED Board Test Rig With PIR

Final board and all wrapped up

Finally all working and wrapped up in cable tidy wrap.

The completed Larson scanner board with its PIR motion detector and LED board all wrapped up neatly with cable tidy. It is powered from a battery box and switch also neatly wrapped. LED Board Completed With PIR All Wrapped Up

I didn’t build the robot that this was going to live in; there is however a Facebook video of the finished piece .

Larson Scanner Arduino sketch

The scanner code came from the incredulist.blogspot .

Also, 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 also helps someone looking to do something similar.

Back_And_Forth_3_With_Motion_Sensor.pde

//
// 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;
}

All of the Arduino code examples can also be found on Forgejo .