I’ve been hard at working coding my own Larson Scanner. This is the iconic sweeping light seen in the Cylons on Battlestar Gallactica or on the front of KITT, the car from Knight Rider.
As I discussed in my previous post, the thing that makes these look neat is the fading tail that chases the brightest light. Originally that was accomplished with capacitors which caused the light to fade as they discharged. I implemented the same concept, using a microcontroller and pulse-width modulation to manage the fading.
After the break I’ll go through the development process and share the code. I did this using an AVR microcontroller but you can use any chip you want. The gist of my process is this:
- Develop software (interrupt) based pulse-width modulation
- Write a function to monitor PWM values and automatically subtract from those over time to cause automatic fading.
- Use a buffer to track which LED is ‘active’ and do not fade that one. As soon as that buffer is shifted the old ‘active’ diode will start to fade.
And of course you’ll want to look at the most recent code.
Update: There are a few improvements that can be made to this system. One is to use exponential values for different intensity levels for a smoother gradient. The other is to swap out Binary Coded Modulation (BCM) for PWM. Read more here.
Hardware:
It seems like I’m constantly building this same circuit on the breadboard. I do most of my prototyping with an ATmega168. Here I’ve connected eight LEDs, each with their own resistor, to the PORTD pins of the microcontroller. I’m using 180 Ohm resistors and I measure about 17mA of current running through each when powered at 5V.
I want to make sure you’ve hooked everything up correctly before starting with the software PWM. Here’s a github tag for that code but the import stuff is here:
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void) {
//Setup IO
DDRD = 0xFF;
PORTD = 0xFF;
while(1){
//Loop Forever
}
}
It gives me a nice set of illuminated LEDs… and now I know the hardware is hooked up correctly.
Adding the PWM
Now let’s get down to work. In order to get the fading effect I need to use Pulse-Width Modulation. This is a fancy way of saying that we need to turn the LEDs on and off very fast. The percentage of on and off time account for different levels of brightness. I’m going to use an internal timer for this. Here’s how I plan to make it work:
- Timer overflows and Interrupt Service Routine starts
- All outputs are updated from buffer set during the last interrupt
- Software counter is incremented.
- Buffer is set for next interrupt by comparing the software counter to each LED’s PWM value. If the counter is higher than that value, the LED should be off, otherwise it should be on
volatile unsigned char pwmValues[8] = {0x00,0x20,0x40,0x60,0x80,0xA0,0xC0,0xE0,0xFF};
volatile unsigned char ledBuffer = 0x00;
ISR(TIMER0_OVF_vect) //Timer0 overflow interrupt handler
{
PORTD = ledBuffer; //Set outputs prewound from last interrupt
static unsigned char pwmCount = 255; //Track overflows to compare with pwmValue
if (++pwmCount == 0)
{
ledBuffer = 0x00;
for (char i=0; i<8; i++)
{
if (pwmValues[i] != 0) ledBuffer |= (1<<i);
}
}
else
{
for (char i=0; i<8; i++)
{
if (pwmCount > pwmValues[i]) ledBuffer &= ~(1<<i);
}
}
}
There are some important things to think about here:
- My variables need to be ‘volatile’ because they may be altered by both the ISR and the main program. That’s not happening yet, but it will later in development
- The ‘static’ keyword used in the ISR initializes the pwmCount variable the first time the program runs, but after that the value will be preserved for future use.
//Setup Timer cli(); //disable all interrupts TCCR0B |= (1<<CS00); //Start timer with no prescaler TIMSK0 |= (1<<TOIE0); //Enable the overflow interrupt sei(); //enable all interrupts
avrdude -P usb -c dragon_isp -p m168 -U lfuse:w:0xE2:m
The last two arguments are the important ones, identifying the chip and burning the fuses. The first few identify by programmer and the port it is on. You’ll need to change that for your setup.
With the program running I get a gradient as expected with the PWM values I loaded into the array. Onwards and upwards, were really getting somewhere now!.
Automatic Fading
The next step in the process is to add a system that automatically changes the PWM values. I’ve written a function to do this for me which I call from the main function based on a timer flag set in the same interrupt taking care of the PWM.
Here’s some important things to remember about the counter and flag system I’m using. Since I’m using an INT as the counter I never want to change that variable outside of the ISR. This is because I would need to disable global interrupts to do so since an INT takes two write processes to change and if the interrupt fired in between those two changes I’d be in trouble. Instead, the ISR will watch for a match value with that counter and use a CHAR variable as a flag for the main function to avoid this issue.
Take a look at the code package at this point in development. I’ve declared my variables:
unsigned int fadeDelay = 624; // 1=32uSecs, 65535(max)=~2.1 secs unsigned char fadeResolution = 8; //Fade steps (between 1 and 255) unsigned int fadeCount = 0; volatile unsigned char fadeFlag = 0;
Added code to the ISR to manage the count and set the flag:
//Track time for fading effect
if (++fadeCount > fadeDelay) {
fadeFlag += 1;
fadeCount = 0;
}
Added code to the loop to watch for a flag and run the function:
if (fadeFlag) //Targer time has passed; time to do something
{
fade();
fadeFlag = 0;
}
And finally I wrote the fading function:
void fade(void)
{
//Systematically reduces all non-zero pwmValues
for (unsigned char i=0; i<8; i++) {
if (pwmValues[i] > 0) {
if (pwmValues[i] < fadeResolution) pwmValues[i] = 0;
else pwmValues[i] -= fadeResolution;
}
}
}
There’s also a little bit of code in there for testing. It just checks to see if all of the LEDs are off and turns them back on again if so.
Adding Scanning for the win
The last piece of the puzzle is to do the scanning. Here’s what I need to happen:
- Light up one LED
- Wait a set length of time
- Turn that LED off and light up the one next to it
- Whenever we run out of LEDs (get to the end of the row) just switch directions
void larson_scanner(void) {
//Adjust scan direction if necessary
if ((1<<0) & scanBuffer) scanDirection = 1;
else if ((1<<7) & scanBuffer) scanDirection = 0;
//Shift the scanBuffer
if (scanDirection) scanBuffer <<= 1;
else scanBuffer >>= 1;
//Write new values to PWM buffer
for (unsigned char i=0; i<8; i++) {
if ((1<<i) & scanBuffer) pwmValues[i] = 0xFF;
}
}
It was also necessary to modify the fade function. That’s because I don’t want to fade the LED that currently has the ‘focus’ of the scanning function. That turns out to be quite easy because the fade function can just check the buffer I’m using with the scanning function. Here’s the altered fade function:
void fade(void)
{
//Systematically reduces all non-zero pwmValues
for (unsigned char i=0; i<8; i++) {
if (pwmValues[i] > 0) {
if (~((1<<i) & scanBuffer)) { //Do not dim active LEDs from scanBuffer
if (pwmValues[i] < fadeResolution) pwmValues[i] = 0;
else pwmValues[i] -= fadeResolution;
}
}
}
}
Fire it up and it works just like in the video. Here’s a direct link to the complete main.c file.
Conclusion:
Once you get elbow deep into this, it’s not hard to grasp the concepts. I’m happy with the outcome; it looks great and it’s fairly adjustable. I would like to look into a couple of different upgrades. It would be nice to make this work with cascading shift registers like 74HC595′s. That would really make expandability an option. I’d also like to give this a try with an Arduino, since I’m rather new to that hardware and I’m not entirely sure how their timing system would mesh with these concepts. Perhaps that’s for a future post.
Resources:
- Source code repository: https://github.com/szczys/Larson-Scanner/

Great tutorial. On a microcontroller without hardware PWM (like the MSP430 Launchpad), a nice way to get the same effect for LEDs is with BCM (Binary Code Modulation), where each I/O line is enabled according to the “weights” of each bit in a byte. http://blog.hodgepig.org/2010/09/12/577/
@Joby: Not a bad method of doing it. The only thing I like better about my method is that I prewind for the next interrupt. This way I can set the LEDs as soon as the interrupt fires, reducing jitter.
Regarding BCM: I was just looking around at the concept thinking this would be an excellent way to implement a scanner using shift registers. When I came upon a 5-page tutorial on BCM that I’ve read before. The example show there is an 8-LED scanner on PORTD (same hardware setup as mine) that uses BCM in the interrupt instead of PWM. The code is on the third page: http://www.batsocks.co.uk/readme/art_bcm_3.htm
Too bad you did not do it with 7400 series
Now, seriously, that would present some technical difficulties, and would be an interesting design (technically speaking).
If you were to implement this with 7400, what approach would you follow ?
Probably a ring oscillator would do the trick.
http://en.wikipedia.org/wiki/Ring_oscillator
or a shift register (eg 74HC595)
I built a chase light (sequencer) using a 555 timer which sends signals to a 7490 for BCD counting works. It feeds a 7442 and 8 LED’s. (The display is a separate card with only 6 wires, (2 for power, 4 for BCD output from the 7490).
Can this be done with ‘Charlieplexing’ to reduce the pin count? How would this get coded?
Yes, it’s possible to do this with a charlieplexed array. I may look into a post about that in the future but for now I’m working on making this expandable by using shift registers, as well as transitioning from PWM over to BCM which is certainly an interesting topic. Here’s a preliminary post on that… but I’ll have something up about shift registers later this week.
Nice tutorial.
I indeed light up my LED using a shift register. Thanks for this.
However, I’m stuck on how I can use pwm with shift register to control my tri-color LED to create a dimming effect and different color.
Can anybody suggest? Flow chart, algo, pseudo code, etc. Anything that lead to my goal.
Your help is very much appreciated.
thanks in advance
Miguelito