The Finite State Machine Elevator

This will be a little different from my usual posts.  No graphics, no detailed step by step directions.  Just some code.  This uses some Arduino but the effort for porting to anything would be pretty minimal.  Don’t forget to match the pin #define statements to your actual hardware, the example ones I used here don’t work for most board types.

This is a pretty standard programming homework assignment, but I never was given this one as a student so when an IRC user asked about it, I thought I’d give it a go.

I’ve only used 3 states, this can be done with more inside the state machine, but I wanted to keep it simple so I split the other aspects into separate tests.

Students; this is not the homework answer you’re looking for.  See the permissions in the copyright.

Without further ado:

  Copyright © Chisight 2018
  Permission is hereby granted, free of shortge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  This software may not be used as a significant portion of the work in 
  completing a school assignment.
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

Hardware is:
  digital pin -- 1k resistor -- button -- ground
  digital pin -- 120 ohm resistor -- white led -- ground

#define SWITCH1 11
#define SWITCH2 12
#define SWITCH3 3
#define SWITCH4 4
#define SWITCH5 5
#define LED1 6
#define LED2 7
#define LED3 8
#define LED4 9
#define LED5 10
#define TOPFLOOR 5
#define IDLELIMIT 10
#define INTERVAL 2000L

enum direction { UP, DOWN, STOPPED }; // this is our main STATE enumeration

unsigned long previousMillis = 0;
short currentFloor=0;                       // computers start counting at zero, unlike humans
short floorSelected[TOPFLOOR]={0,0,0,0,0};  // array of buttons, initalized to not pressed
enum direction elevatorDirection=STOPPED;   // this is our STATE variable
short moved;
unsigned int idleCount=0;

void setup(){
  pinMode(SWITCH1, INPUT);      // set pin to input
  digitalWrite(SWITCH1, HIGH);  // turn on pullup resistor
  pinMode(SWITCH2, INPUT);      // set pin to input
  digitalWrite(SWITCH2, HIGH);  // turn on pullup resistor
  pinMode(SWITCH3, INPUT);      // set pin to input
  digitalWrite(SWITCH3, HIGH);  // turn on pullup resistor
  pinMode(SWITCH4, INPUT);      // set pin to input
  digitalWrite(SWITCH4, HIGH);  // turn on pullup resistor
  pinMode(SWITCH5, INPUT);      // set pin to input
  digitalWrite(SWITCH5, HIGH);  // turn on pullup resistor

  pinMode(LED1, OUTPUT);    // set pin to output
  digitalWrite(LED1, LOW);  // turn LED off
  pinMode(LED2, OUTPUT);    // set pin to output
  digitalWrite(LED2, LOW);  // turn LED off
  pinMode(LED3, OUTPUT);    // set pin to output
  digitalWrite(LED3, LOW);  // turn LED off
  pinMode(LED4, OUTPUT);    // set pin to output
  digitalWrite(LED4, LOW);  // turn LED off
  pinMode(LED5, OUTPUT);    // set pin to output
  digitalWrite(LED5, LOW);  // turn LED off

void printEnum(enum direction elevatorDirection){
        case UP :
        case DOWN :
        case STOPPED :
        default :
          Serial.print("Out of range!");

void setLED(short currentFloor){
  Serial.print("3 setting LED:");
  Serial.println(itoa(currentFloor+1,(char*)"  ",10));
  digitalWrite(LED1,(currentFloor==0)?HIGH:LOW); // set LED on or off if currentFloor
  digitalWrite(LED2,(currentFloor==1)?HIGH:LOW); // set LED on or off if currentFloor
  digitalWrite(LED3,(currentFloor==2)?HIGH:LOW); // set LED on or off if currentFloor
  digitalWrite(LED4,(currentFloor==3)?HIGH:LOW); // set LED on or off if currentFloor
  digitalWrite(LED5,(currentFloor==4)?HIGH:LOW); // set LED on or off if currentFloor

void openDoors(short currentFloor){
  unsigned long currentMillis;
  Serial.print("5 Open doors at floor:");
  Serial.println(itoa(currentFloor+1,(char*)"  ",10));
  do {
    currentMillis = millis();
  } while(currentMillis - previousMillis = INTERVAL) {  // is it time to move?
    previousMillis = currentMillis;     // save the time to have it for the next cycle
    moved=0;                            // we haven't moved during this cycle yet
      case UP :                         // we're moving upwards
        if(currentFloor<TOPFLOOR){      // if we're not at the top yet
          for(short i=currentFloor+1; i0){             // if we're not at the bottom yet
          for(short i=currentFloor-1; i>=0; i--){ // for each floor below us
            if(floorSelected[i]){       // check if selected
              setLED(--currentFloor);   // move down one and set led for that floor
              moved=1;                  // we've not stopped yet
              break;                    // only move one floor per cycle
            floorSelected[currentFloor]=0; // we've arrived, clear the button
      case STOPPED :
        for(short i=0; icurrentFloor?UP:DOWN); //head towards that floor
              moved=1;                  // avoid stopping before we even moved
    if(!moved) elevatorDirection=STOPPED;
    else idleCount=0;
    if((elevatorDirection==STOPPED) && (currentFloor != 0)) idleCount++;   // if stopped, count seconds
    if(idleCount>IDLELIMIT) {                     // once over the preset limit
      floorSelected[0]=1;                         // return to the ground floor
    Serial.print("2 elevatorDirection:");
    Serial.print(" currentFloor:");
    Serial.print(itoa(currentFloor+1,(char*)" ",10));
    Serial.print(" buttons:");
    Serial.print(itoa(floorSelected[0],(char*)"  ",10)); 
    Serial.print(itoa(floorSelected[1],(char*)"  ",10)); 
    Serial.print(itoa(floorSelected[2],(char*)"  ",10)); 
    Serial.print(itoa(floorSelected[3],(char*)"  ",10)); 
    Serial.print(itoa(floorSelected[4],(char*)"  ",10));
    Serial.print(" idleCount:");
  if(!digitalRead(SWITCH1)){ if(floorSelected[0]==0) Serial.println("SWITCH1 pressed"); floorSelected[0]=1; } // read the buttons and adjust the floor if needed
  if(!digitalRead(SWITCH2)){ if(floorSelected[1]==0) Serial.println("SWITCH2 pressed"); floorSelected[1]=1; }
  if(!digitalRead(SWITCH3)){ if(floorSelected[2]==0) Serial.println("SWITCH3 pressed"); floorSelected[2]=1; }
  if(!digitalRead(SWITCH4)){ if(floorSelected[3]==0) Serial.println("SWITCH4 pressed"); floorSelected[3]=1; }
  if(!digitalRead(SWITCH5)){ if(floorSelected[4]==0) Serial.println("SWITCH5 pressed"); floorSelected[4]=1; }

That’s all there is to it.


Installing SDCC on Debian or Ubuntu


This blog revision covers 3.6.9-10221 but should work for any fairly close version number.  I’m using a vanilla Debian Stretch on x86_64 architecture.

I’m specifically excluding PIC processors at this time, they have additional prerequisites and include non-free code.  I also do not cover the additional regression tests for the Z80.


sudo apt install build-essential libboost-all-dev bison flex texinfo


Go to and select a version under “SDCC Source Code (sdcc-src)”   note: the current snapshot may not be functional software and may not even build.

Or download the version I used with:

cd ~/Downloads
sdccver="20180206-10225"; wget -O sdcc.$sdccver.tar.bz2$sdccver.tar.bz2/download#

Unpack with:

tar -xvjf sdcc.$sdccver.tar.bz2


./configure --disable-pic14-port --disable-pic16-port



Check for errors in the above, it should be clean but building can sometimes go wrong.  If you find errors, run make clean, fix the problem and re-run make.


sudo make install

Check the version to make sure it’s the one you installed with:

sdcc -v

That’s it!

Stop by and visit at irc://

A color changing desk lamp using ws2801 strip LEDs and a ch340g USB serial adapter


I’ve been using Redshift on my desktop for awhile now and have wanted my keyboard light to change color at night as well.  I’d been using a small string of x-mas lights because they offered a nicely spread out light that wasn’t excessively bright at night but they weren’t adjustable in color or brightness.

This project requires soldering, including soldering two small wires to the pins of an SOIC-16 chip.  Even with cheap, poor quality tools, it’s possible, though I did use a solder puller to clean up my first attempt.

Ebay and China sellers have shorter 5 volt ws2801 led strips for under $5 and the USB to serial adapters are only about 60 cents.   My preference is the ch340g serial adapters over FTDI, they have no history of abusing customers who unknowingly bought clones, and the drivers are built in to Linux.

The ws2801 was chosen over the ws2811B due to the tight timing restrictions of the ws2811B, controlling those is not very practical without a micro-controller which would double the price of this project, so make sure you get the right one.

The Hardware:

  • If your strip has wires attached, identify the input end.  Then identify the colors for each pin.  Mine were Blue = Ground, Red = Data, Green = Clock, and Black = +5V.  You can use a meter or continuity test to find Ground and +5V, but you’ll have to look under the shrink tubing to identify Data and Clock.
  • Solder wires to pins 13 and 14 of the ch340g chip on the USB serial adapter.  13 will be Data and 14 will be Clock.  If you’re using the stranded wires that came with your LED strip, you may have to remove a few strands to get the wire to fit on the pin.  I was down to about 5 strands for mine.  Twisting and tinning these wires first helps.
  • Solder wires to the Ground and +5 terminals, also soldering the voltage select terminal to the 5V terminal on the USB serial adapter.CH340G_Adapter
  • Solder all 4 wires to the LED strip (if they weren’t already attached)
  • Cut your strip to length.  Each LED set uses up to 60 milliamps and USB only provides 500 milliamps, so that works out to 8 1/3 RGB LEDs.  I went with 9 for my strip and use nothing else on that port, even though that’s pushing it a little bit.

The software:

Save this code as ws2801write.c and compile with:

 gcc -o ws2801write ws2801write.c


/* 32 LEDs is 9.6W or 1.92A
500mA USB is 8.33 LEDs

The pixels are connected by a 4-conductor cable.
+5V (Red)
Ground (Blue)
Data (Yellow) -- DTR
Clock (Green) -- RTS

Clock starts low for at least 500uS.

Data is clocked out 24 bits at a time, in Red, Green, Blue order with msb sent first.

Each bit is captured on rising edge of clock.

Data is latched by holding clock pin low for 1 millisecond.

  ws2801write [ -p  ] [ -l  ] [ -c D[TR] | R[TS] ] [ -d D[TR] | R[TS] ]    [    ]
    -p serial device file, defaults to /dev/ttyUSB0
    -l number of pixels in the string, defaults to 64
    -c clock pin, defaults to RTS
    -d data pin, defaults to DTR
       sequences are decimal 0-255 each and when fewer sequences are provided than
      , the final sequence is repeated until  after the sequences run out

LED string:
Datasheet: Page 12 of .
Additional information:
   end clock:
   DTR/RTS in linux:

    #include        /* File Control Definitions           */
    #include      /* POSIX Terminal Control Definitions */
    #include       /* UNIX Standard Definitions          */
    #include        /* ERROR Number Definitions           */
    #include    /* ioctl()                            */

The syntax is:

ws2801write [ -p  ] [ -l  ] [ -c D[TR] | R[TS] ] [ -d D[TR] | R[TS] ]    [    ]
    -p serial device file, defaults to /dev/ttyUSB0
    -l number of pixels in the string, defaults to 64
    -c clock pin, defaults to RTS
    -d data pin, defaults to DTR
       sequences are decimal 0-255 each and when fewer sequences are provided than
      , the final sequence is repeated until  after the sequences run out

Examples:   9 full on red LEDs:
./ws2801write -l 9 255

9 bright white (a bit blueish on mine)
./ws2801write -l 9 255 255 255

3 bright blue, 3 bright green, and 3 bright red
./ws2801write 0 0 255 0 0 255 0 0 255 0 255 0 0 255 0 0 255 0 255 0 0 255 0 0 255 0 0

9 dim redish
./ws2801write -l 9 127 80 35

9 medium redish
./ws2801write -l 9 255 200 100

Note, some machines may interrupt the transfer process and cause odd colors.  nice -20 ./ws2801write -l 9 255 255 255 may resolve this OS problem.

DIY Charging a Craftsman C3 Lithium Ion battery pack

C3 Battery
To charge DieHard / Craftsman C3 lithium-ion battery packs apply 21V across the + and – terminals. (rounded end = +, opposite = -)

C3 Connector C3 batteries come in several variations and each one has different requirements to allow charging.   Most variants will need a 22K resistor between the left terminal (when looking with the curve away from you) and the + terminal.

Be sure to use a current limited supply to avoid the possibility of damaging your pack or starting a fire should the internal protection circuit fail.

My model was a 315.PP2025 (130211023) rated 19.2V, 48watt hours purchased in mid 2016 and required the 22K resistor between the left terminal and the + terminal to charge without cutting off after 30 seconds.

22K is red red black black brown for 1% 5 band resistors, and red red orange gold for 5% 4 band resistors.

C3 Circuit For charging in the house, I’ve got a 24V 0.5A wall wart (more of a brick) power supply and an LM317 regulator in a TO220 package.  I used three 10 ohm resistors in parallel to spread the heat out so that I could use 1/4 watt resistors without getting them too hot.  (The LM317 will get too hot to touch, be careful.)  The circuit is to the left and limits current to 375mA which, for my 48W pack, is a nice gentle 6 hours charge from empty and generates no heat in the pack that my IR thermometer can detect.  The LM317 will go up to 1.5A with a big heat sink and seven 5.6 ohm 1/4 watt resistors in parallel, but be careful to not charge packs that are below 5V at this high rate.  The 1.5A charger will fill the big 48W pack in 1 hour 40 minutes.

The 375mA charge rate is safe for even deeply discharged batteries that won’t charge in the Craftsman charger.

Why is my weather radar image out of date?

RADAR image

Clicking on NOAA’s radar images gives you weather that ranges from 5 minutes out of date to around 70 minutes out of date.  Ever wonder why such a range and why they’d give us weather images that are so old?

A little while back, I was working for a major retailer and had the opportunity to see how Content Delivery Networks (CDNs) were used in the real world, and just what sorts of problems they caused.  It turns out that NOAA uses a CDN called Akamai and that they’ve mis-configured it so that static content and quickly changing content are all cached for the same time rather than allowing the dynamic content to be handled specially.  To top that off, their complaint email address is uninterested in resolving the problem.  In the several years since I noticed the solution, they have improved the most local radar images so that they are only caching about 3 times longer than they should and they have still not implemented an update trigger system that cause the CDN to get new content as soon as it is available.

There is good news.  Due to the simplistic way NOAA is using their CDN, we can force the CDN to poll for updated content just for us by adjusting the URL slightly.  The full national radar loop is normally accessed via, but by changing that to, where the “38” is the current minute then the image will update every 10 minutes on the 8s.  The radar is still slightly out of date, but only because it takes a few minutes for NOAA to actually process the data and build the images for the Web and not because of CDN caching problems.  This works because Akamai sees the ?x=38 part and considers the complete URL to be different and therefore polls NOAA for updated data.  Adding ?x=38 (again 38 is just an example, use the current minute) to the end of any or URL that should update but doesn’t, will resolve the problem.

If you’d like the problem fully resolved, contact your congressman and tell them that NOAA is wasting money by updating their weather images but not pushing those updates to Akamai so that you can see them.