Last week I had a detailed Arduino tutorial on software pulse width modulation using millis() and micros(). Why? Because I wanted to create a Proper Larson Scanner, with persistence and at least 8 LEDs.

KITT larson scanner example
From Amazon

Even though it is a popular project for the Arduino Uno, most Larson scanner tutorials, like my first one, have a few flaws. First, there is no persistence, or tail, to the LED as it moves back and forth. Persistence could be solved by using pulse-width-modulation. The Uno and other 328p-based micros only have 6 Pulse Width Modulation (PWM) pins. And let’s be honest, every project is made better by adding more LEDs. 🙂

If you look at this cover shot of KITT from Knight Rider you will see there isn’t just a single light source. It appears multiple lights are turned on, as well as fading effect. This fading effect creates a tail. Of course, the reason is probably that standard light bulbs were being used back in the 80s. Traditional light bulbs don’t turn on or off nearly as fast as LEDs.

Presenting the Proper Larson Scanner

Knowing that a popular Halloween hack is to add Cylon (or KITT) lights to your pumpkins, I thought it was time for a Proper Larson Scanner. This code example does a couple of important things.

  1. It implements my “software pulse width modulation.”
  2. Can be used on all 20 I/O pins of an Uno (or other 328p Arduino)
  3. Does not use any delay()s!

So if you want to make your pumpkin even more Cylon-like this Halloween, check out this full tutorial on a proper Larson scanner.

Proper Larson Scanner Video

Very short demo video of what this code does. (Sound effects are not part of the code, obviously.)  Pay careful attention to the persistence of the LEDs. Red LEDs are tough to work with since they aren’t very bright. If your project has a short lifespan, you might consider selecting a current limiting resistor that drives the LED harder than 20mA. (Just make sure you use a current limiting resistor!)

Full Larson Scanner Example Code

Here is the full code (collapsed) or you can also grab it out of my pastebin.

//proper-larson.ino
// macros for LED state
#define ON true
#define OFF false

#define LEFT true
#define RIGHT false

// variables for larson pattern timing
unsigned long currentMillis = millis();
unsigned long previousMillis = 0;
unsigned long millisInterval = 5;

// LED fading (recommended value)
int fadeIncrement = 5;

// tells which LED is the brightest
// start at the far "right" (or left?)
int larsonStatus = 0x80;

// bit mask for maximum scan value
// picture larsonMax in binary
// 00001000 00000000
// if you had 30 LEDs, you'd need an a long...
int larsonMax = 0x80;
// arbitrary starting direction
bool direction = RIGHT;

// variables for software PWM
// really have to play with pwmMax
// and microInterval to get a smooth
// fade effect
unsigned long currentMicros = micros();
unsigned long previousMicros = 0;
unsigned long microInterval = 10;
// instead of HW PWM's "255" levels, we get 100
const byte pwmMax = 100;

// typedef for properties of each sw pwm pin
typedef struct pwmPins {
   int pin;
   int pwmValue;
   bool pinState;
   int pwmTickCount;
} pwmPin;

// create the sw pwm pins
// these can be any I/O pin
// that can be set to output!
const int pinCount = 8;
//const byte pins[pinCount] = {2,3,5,6,9,10,11,12};
const byte pins[pinCount] = {2,3,4,5,6,7,8,9};

// an array of Software PWM Pins
pwmPin myPWMpins[pinCount];

void setup() {
   // sets each element of myPWMpins to something
   setupPWMpins();
}

void loop() {
   // constantly ping handlePWM()
   handlePWM();

   // typical millis timer code
   // see what time it is.
   currentMillis = millis();
   if (currentMillis - previousMillis >= millisInterval) {
      if (direction == LEFT) {
         // going "left"
         // need a reference for which LED is the "bright" one
         int index = 0;
         for (index=pinCount; index >= 0; index--) {
            if (bitRead(larsonStatus, index))
            break;
         }
         // index is the bit set to "1"

         // see if we're the last pin
         if (index < (pinCount)) {
            // fade up the LED next to the "on" LED
            myPWMpins[index+1].pwmValue += fadeIncrement;

            // check if the fading LED is fully on yet
            if (myPWMpins[index+1].pwmValue >= pwmMax) {
               // okay, time to move to the next one!
               // "barrel shift" the "1", one bit over
               larsonStatus = larsonStatus << 1;
               // start fading the previous on
               myPWMpins[index].pwmValue   = myPWMpins[index].pwmValue - fadeIncrement;
               // did we hit the end yet?
               if (larsonStatus == (larsonMax << 1)) {
                  // okay, time to change direction
                  larsonStatus = larsonMax;
                  direction = RIGHT;
               }
            }
         }
         // create the tails.
         createFadeTail((index-1),4,15);
         createFadeTail((index-2),2,5);
         createFadeTail((index-3),1,1);
      } else {
         // going right
         int index = 0;
         // need a reference for which LED is the "bright" one
         for (index=0; index < pinCount; index++) {
            if (bitRead(larsonStatus, index))
            break;
         }
         // index is the bit set to "1"

         if (index > 0) {
            // fade up the LED next to the "on" LED
            myPWMpins[index-1].pwmValue += fadeIncrement;

            if (myPWMpins[index-1].pwmValue >= pwmMax) {
               larsonStatus = larsonStatus >> 1;
               // start fading the previous on
               myPWMpins[index].pwmValue   = myPWMpins[index].pwmValue - fadeIncrement;
               // didn't use a const here, because, it is probably 0
               if ((larsonStatus == 0x01)) {
                  direction = LEFT;
               }
            }
            // very similar to opposite direction
            // except we are modifying pins "after" bright one
            createFadeTail((index+1),4,15);
            createFadeTail((index+2),2,5);
            createFadeTail((index+3),1,1);
         }
      }
      // setup clock for next tick
      previousMillis = currentMillis;
   }
}

void createFadeTail(int pinIndex, int fadeMultiplier, int pwmLimit) {
   // make sure we aren't modifying past the end of myPWMpins or pins array
   if ((pinIndex) < pinCount) {
      // change pwmValue to make our fade effect
      myPWMpins[pinIndex].pwmValue = myPWMpins[pinIndex].pwmValue - fadeIncrement*fadeMultiplier;
      // make sure the number doesn't go negative
      if (myPWMpins[pinIndex].pwmValue <= fadeIncrement)
      // limit the lowest brightness
      // found this looks better with "1-5" instead of 0
      myPWMpins[pinIndex].pwmValue = pwmLimit;
   }
   handlePWM();
}

void handlePWM() {
   currentMicros = micros();
   // check to see if we need to increment our PWM counters yet
   if (currentMicros - previousMicros >= microInterval) {
      // Increment each pin's counter
      for (int index=0; index < pinCount; index++) {
         // each pin has its own tickCounter
         myPWMpins[index].pwmTickCount++;

         // determine if we're counting on or off time
         if (myPWMpins[index].pinState == ON) {
            // see if we hit the desired on percentage
            // not as precise as 255 or 1024, but easier to do math
            if (myPWMpins[index].pwmTickCount >= myPWMpins[index].pwmValue) {
               myPWMpins[index].pinState = OFF;
            }
         } else {
            // if it isn't on, it is off
            if (myPWMpins[index].pwmTickCount >= pwmMax) {
               myPWMpins[index].pinState = ON;
               myPWMpins[index].pwmTickCount = 0;
            }
         }

         // for the LEDs on or off, for min and max values
         // similar to how Arduino library does PWM
         if (myPWMpins[index].pwmValue == pwmMax)
            myPWMpins[index].pinState = ON;
         if (myPWMpins[index].pwmValue == 0)
            myPWMpins[index].pinState = OFF;

         // could probably use some bitwise optimization here, digitalWrite()
         // really slows things down after 10 pins.
         digitalWrite(myPWMpins[index].pin, myPWMpins[index].pinState);
      }
      // reset the micros() tick counter.
      previousMicros = currentMicros;
   }
}

// function to "setup" the sw pwm pin states
// modify to suit your needs
void setupPWMpins() {
   for (int index=0; index < pinCount; index++) {
      // connect a myPWMpin to an actual pin
      myPWMpins[index].pin = pins[index];
      // start with all LEDs off
      myPWMpins[index].pwmValue = 0;
      // doesn't matter ON or OFF
      myPWMpins[index].pinState = ON;
      // set the counter to 0
      myPWMpins[index].pwmTickCount = 0;
      // make sure pin is set correctly
      pinMode(pins[index], OUTPUT);
   }
   // start with the "go-right" pattern, so
   // turn on "last" LED full
   myPWMpins[pinCount-1].pwmValue = 100;
}

// Code from [email protected]
// email | twitter | www
// See more at: https://www.baldengineer.com/larson

Definitions

// line 8
#define ON true
#define OFF false
#define LEFT true
#define RIGHT false

These are simple text macros to make some of the code easier to read. LEFT and RIGHT are somewhat arbitrary. Depending on how you orient your LEDs, these may be “backward (to you).”

// line 10
// variables for larson pattern timing
unsigned long currentMillis = millis();
unsigned long previousMillis = 0;
unsigned long millisInterval = 5;

The fadeIncrement variable is how much to change the PWM of the LEDs. Depending on how pwmMax is set (on line 38), you can play with this value. You’ll get more “precision” with a lower number, but the fades will be slightly slower. I found 5 was a pretty good trade-off.

// line 18
// tells which LED is the brightest
// start at the far "right" (or left?)

int larsonStatus = 0x80;

// bit mask for maximum scan value
// picture larsonMax in binary
// 00001000 00000000
// if you had 30 LEDs, you'd need an a long...
int larsonMax = 0x80;
// arbitrary starting direction
bool direction = RIGHT;

The variable larsonStatus defines which “bit” or “LED” is fully “on.” Remember, to make a proper Larson scanner, you need to emulate incandescent light bulbs. This type of bulb has a slight “fade up” when it turns on. The variable defines the currently “on” LED. Later, in the code, we will fade the LED “next” to it. With an int variable type, you can have up to 16 LEDs since an int is 16 bits wide. For more LEDs, you’ll need a larger data type.

The value “0x80” is the same as 0000 0000 1000 0000 in binary. This value means we are starting at the “left” position.

The variable type of larsonStatus and larsonMax need to be the same. Since the int has 16 bits, but my code is only using 8 bits, we need to tell the scanning code when to stop going “up” (or “left”). If you added more LEDs, you’ll need to set larsonMax to a larger data type.

For direction, bool is used since the direction can only be LEFT or RIGHT.

// line 30
// variables for software PWM
// really have to play with pwmMax
// and microInterval to get a smooth
// fade effect
unsigned long currentMicros = micros();
unsigned long previousMicros = 0;
unsigned long microInterval = 10;
// instead of HW PWM's "255" levels, we get 100
const byte pwmMax = 100;

// typedef for properties of each sw pwm pin
typedef struct pwmPins {
	int pin;
	int pwmValue;
	bool pinState;
	int pwmTickCount;
} pwmPin;


These variables control the software PWM. Since we don’t have enough hardware PWM pins, we’ll use software PWM to do all of the dimmings of the LEDs. Tracking the status of each of the software PWM pins is made easier with this struct.

For more information on this section, check out my tutorial on software PWM.

// line 48
// create the sw pwm pins
// these can be any I/O pin
// that can be set to output!
const int pinCount = 8;
//const byte pins[pinCount] = {2,3,5,6,9,10,11,12};
const byte pins[pinCount] = {2,3,4,5,6,7,8,9};

// an array of Software PWM Pins
pwmPin myPWMpi

If you have more than eight pins in your scanner, add them to this array and set the value of pinCount appropriately. In my case, I’m using 8 I/O pins. Notice that not all of the pins are the Uno’s hardware PWM!

setup ()

// line 59
void setup() {
   // sets each element of myPWMpins to something
   setupPWMpins();
}

Nothing exciting here for setup(). Just calls a function that sets up the initial state of the SW PWM pins.

loop()

// line 65
// constantly ping handlePWM()
handlePWM();

For each iteration of the loop(), we call the function handlePWM(). This function is where all of the SW PWM magic occurs. Since SW PWM relies on micros(), call this function whenever you have big chunks of code, like for-loops or while-loops. If, for some reason, you wanted to use delay(), you need to be careful. delay() will cause SW PWM to fail.

To use delay() with SW PWM, you’d need to break up your delay() inside of a for-loop(), calling handlePWM() on each iteration. My millis() multitasking tutorial covers this delay()-based alternative to using millis().
// line 70
currentMillis = millis();
if (currentMillis - previousMillis >= millisInterval) {

Typical millis() code. Checking to see if it is time to increase the fade value of the LEDs. For this code, I found 5ms to work pretty well.

// line 72
if (direction == LEFT) {

Depending on our current direction, we’ll handle the code slightly differently. A clever programmer could probably cut this stuff down. I’m not going to repeat my explanations for “RIGHT.” It’s the opposite of “LEFT.”

// line 75
int index = 0;
for (index=pinCount; index >= 0; index--) {
   if (bitRead(larsonStatus, index))
      break;
}

We need to know which bit is the “on” bit in the larsonStatus variable. The value of index corresponds to an LED on our board. There might be a better way to do this, but I found a for-loop works fine. Once we find the “1” inside of larsonStatus, the loop exits, leaving the value of the index variable to be the current “on” LED! The next code fades the LED “next to” the on LED.

// line 83
if (index < (pinCount)) {
   // fade up the LED next to the "on" LED
   myPWMpins[index+1].pwmValue += fadeIncrement;

The index value should never be higher than the number of pins. I don’t have any “exception handling code” here. So this is just to make sure we don’t go off in the weeds. You could add something around line 101 that sets the index variable to something sane, but I haven’t tried that yet.

The increase to the pwmValue on line 85 fades the LED “next to” the on LED. If you look at the RIGHT code, you’ll see we do “index-1”. (Like I said, you could probably be clever and use a single set of code. I didn’t.)

// line 88
if (myPWMpins[index+1].pwmValue >= pwmMax) {
   // okay, time to move to the next one!
   // "barrel shift" the "1", one bit over
   larsonStatus = larsonStatus << 1;
   // start fading the previous on
   myPWMpins[index].pwmValue = myPWMpins[index].pwmValue - fadeIncrement;

When the LED “next to” the “on” LED is fully turned on, it’s time to move to the next LED. The code does a “barrel shift” of the larsonStatus variable. In other words, the 1 moves to the “next” bit of larsonStatus.

Barrel Shift Note

Sometimes people confuse “<< 1” with moving 1 bit and making the value of the least significant bit (LSB) “1”. The number in this expression is: how many times to shift the bit in that direction. The shift is loaded with zeros. In the case of the scanner, that works fine.

// line 95

if (larsonStatus == (larsonMax << 1)) {
   // okay, time to change direction
   larsonStatus = larsonMax;
   direction = RIGHT;
}

This code is a little different from left and right, but the logic is the same. When the LarsonStatus variable gets to the “end”, we need to change scanning directions.

// line 103
createFadeTail((index-1),4,15);
createFadeTail((index-2),2,5);
createFadeTail((index-3),1,1);

To give a proper trailing effect, we need the LEDs “after” the “on” LED to fade out. I had a lot of trouble with these values. So you’ll need to tweak them yourself. In the section below, I’ll explain what each argument does in more detail. For now you need to pass createFadeTail() three things: which LED, the fadeIncrement multiplier, and the floor or smallest value to set the LEDs.

Why do you need a floor or minimum value?

Watch the Knight Rider or Cylon scanning closely. One thing you might notice is that it appears like there is light coming from ALL of the scanners. So to make this Larson scanner more correct, the LEDs glow just slightly instead of turning off completely.

Scanning RIGHT

Again, I’m not going to explain the RIGHT scanning in detail. It’s effectively the opposite of the LEFT, with a few tweaks in the logic. If you have a question, leave a comment.

createFadeTail()

This function gives the LEDs a tail-like glow. This effect is the reason I wanted to make a proper Larson scanner in the first place! There is some persistence in the scanning, which is what this function emulates.

// line 141
void createFadeTail(int pinIndex, int fadeMultiplier, int pwmLimit) {
	// make sure we aren't modifying past the end of myPWMpins or pins array
	if ((pinIndex) < pinCount) {
		// change pwmValue to make our fade effect
		myPWMpins[pinIndex].pwmValue = myPWMpins[pinIndex].pwmValue - fadeIncrement*fadeMultiplier;
		// make sure the number doesn't go negative
		if (myPWMpins[pinIndex].pwmValue <= fadeIncrement)
			// limit the lowest brightness
			// found this looks better with "1-5" instead of 0
			myPWMpins[pinIndex].pwmValue = pwmLimit;
	}
	handlePWM();
}

Line 143 makes sure we aren’t accessing an element outside of the myPWMpins array like we did inside of loop().

The increment on line 145 should look familiar. The only difference is that we’re multiplying the fadeIncrement by a number. I found 4, 2, 1 to be the right sequence of the tail. Your mileage may vary (YMMV).

Since we’re using a multiplier, it is possible the pwmValue will drop below zero. (This is also why it is a “signed integer”.) The third argument of this function, pwmLimit, is the “floor” value. This limit forces the LED to a known state.

The handlePWM() call may or may not be necessary. However, I have a habit of calling these “periodic” style functions from other functions. That way, if the original function calls end up in a loop, I know the software PWM won’t be affected (much).

handlePWM()

Lines 156 through 194 are the heart of Software PWM. Read my tutorial on software PWM to understand how it works.

setupPWMpins()

// line 199
void setupPWMpins() {
for (int index=0; index < pinCount; index++) {
	// connect a myPWMpin to an actual pin
	myPWMpins[index].pin = pins[index];
	
	// start with all LEDs off
	myPWMpins[index].pwmValue = 0;

	// doesn't matter ON or OFF
	myPWMpins[index].pinState = ON;

	// set the counter to 0
	myPWMpins[index].pwmTickCount = 0;

	// make sure pin is set correctly
	pinMode(pins[index], OUTPUT);
}

Nothing too unique happening here. To initialize the myPWMpins array, I use a for-loop() to go through and set each value to a known state. That state is off, by the way. Don’t be confused by line 205’s “pinState = ON”. That’s for the PWM code, not for the actual state of the LED.

Line 209 sets the actual pins to OUTPUT. (Arduino’s analogWrite() does this for you.)

// line 213
myPWMpins[pinCount-1].pwmValue = 100;

Again, nothing too unique. We’re just forcing the “left most” bit and pin fully on. The initial state of this scanner is to start going “RIGHT.”

Conclusion

If you want to customize your lights, here are some ideas. These are exercises for the reader that I might do as future tutorials. If you’re interested in one, leave a comment.

  • Add buttons (or serial commands) to speed up, slow down, or pause the animation.
  • Create a small amount of “cell leakage” to the LED immediately next to the one “turning-on.” (You can see that in the KITT picture.)
  • Use the same code on a NeoPixel Digital RGB Strip from Adafruit
Author

Fan of making things beep, blink and fly. Created AddOhms. Stream on Twitch. Video Host on element14 Presents and writing for Hackster.IO. Call sign KN6FGY.

18 Comments

  1. Paul Bakker Reply

    This is exactly what I was looking for!! And i got it to work, which is a big deal for me. Pretty new to Arduino. Quick question, is it possible to get this code on a ATtiny84? I’ve tried but it only works one way and than it stops. I’m making a model car with a larson scanner on the front. I just want to be able to switch it on and off. Probably not enough memory, I think

    • This code is probably overkill for your project. I would just use the traditional Larson examples which do not use PWM.

  2. Is it possible to adapt this to a shift register? This is by far the most accurate scanner tutorial I’ve found I just need some more pins on my Arduino. Thanks for your work on this. Making a walking stick for my 5 year old son with an embedded scanner and other fancy bits. This has been a great help

  3. Steven M Gouge Reply

    Trying to do this with only 5 leds. After the first sweep left and right it hangs with all leds on and the led in pin6 is the long bright one. What am I missing?

      • Steven M Gouge Reply

        Yes, I did. Its stumping me becase 1 time after letting it sit for a minute it started working perfect. Now its back to not working again, with no change to the code. Starting to wonder if I have a hardware issue

        • Did you change larsonMax to a value appropriate for your number of LEDs? (0010 0000 = 0x20, instead of 0x80).

    • I have an 8 LED setup and mine is exhibiting this same behaviour. Shifts from left to right, then begins to come back 1 LED and stops. If i wait long enough, it eventually lights another led maybe. very strange.

    • It’s just LEDs connected to an Arduino (with current limiting resistors.) Pins numbers are in the code.

  4. Craig Holdsworth Reply

    Excellent tutorial.
    Super helpful.
    I am trying to make a larson scanner that shifts with a distance measurement from an ultrasonic sensor. Any tips. Really struggling.

  5. Back in 1986 I built and installed a 24 light KITT system using bidirectional shift registers and a timer. Worked fairly well and the bulbs did give a trailing effect. Just got back into this stuff after 30 years and prototyped three shiftregisters driven by the arduino doing a similar thing. Would appreciate some feed back. Thanks for sharing all your knowledge, this is a great site!

Write A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.