Sequence Controller

The Sequence Controller is an 8 or 16 channel PWM controller for driving DC loads up to 3 amps. It uses an ESP32 development board to drive it, and one controller board for each 8 outputs. It can be provisioned and programmed using any WiFi compatible device that supports web browsing.

It is programmed using a custom sequencing language that supports looping.

Provisioning

Before you can connect the controller, you will need to provision it to connect to your wireless network.

  1. Go to the list of wireless networks on your device.
  2. Connect to the network with the name "SequenceController", using the password "12345678".
  3. Open a web browser. This should take you to the provisioning page. If not, navigate to http://192.169.4.1
  4. Choose your wireless network from the list,

The controller will attempt to connect to your wireless network.

Fade - the Sequence Controller language

The animation speed is 100 Hz, so each cycle is 10 milliseconds.

You can find a demo of the language here:

Dim is a simple language for writing animations. This document will introduce Dim through a set of examples.

Example: Dim down and dim up

D(100,1,1.0)
A(100)
D(100,1,0.0)
A(100)

The D command sets up an animation on a specific channel. It looks like this:

D(<cycle-count>, <channel>, <brightness>)

where:

<cycle-count> is the number of “ticks” it should take for the brightness change to happen. There are 100 ticks in a second.

<channel> is the device to change the brightness on. The number of  channels depends on the physical hardware being used, and for a device with N channels, they are numbered 0 through N-1.

<brightness> is the desired ending brightness, expressed as a floating point number between 0.0 (off) and 1.0 (fully on).

In the example, the cycle count is 100, so each animation will take 1 second.

The A command tells the system to perform animations:

A(<cycle-count>)

<cycle-count> is the number of ticks to animate

In pseudo-code, we can describe the animation as follows:

  1. Drive channel #1 to 1.0 (fully on) over 1 second.
  2. Execute that animation for 1 second.
  3. Drive channel #1 to 0.0 (fully off) over 1 second.
  4. Execute that animation for 1 second.

Example: Multiple animations at once

D(100,1,1.0)
D(100,2,0.0)
A(100)
D(100,1,0.0)
D(100,2,1.0)
A(100)

In this example, we add in a second channel animating at the same time.

It is quite common to do this sort of thing, so Dim allows it to be expressed in a simpler form:

D(100,1,1.0,2,0.0)
A(100)
D(100,1.0,0,2,1.0)
A(100)

Any number of animations can be expressed in a single D command; the only constraint is that they all must use the same cycle count.

Example: Immediate commands

DI(100,1,1.0,2,0.0)
DI(100,1.0,0,2,1.0)

The DI command combines the D and A commands into a single command.

Looping

Here’s another program:

FOR channel 0:7
   DI(100,channel,1)
   DI(100,channel,0)
ENDFOR

The FOR statement defines a variable named “channel” and says that it will get the values 0 through 7.

By default, the number in a FOR loop is increased by 1 each time through the loop. A different number could be specified:

FOR channel 0:7:2

or we could even us a negative number:

FOR channel 7:0:-1

It is possible to nest FOR loops:

FOR cycle 100:10:-30
   FOR channel 0:7
     DI(cycle,channel,1)
     DI(cycle,channel,0)
   ENDFOR
ENDFOR

In this example, the outer for loop will increment the cycle count from 100 to 70, 40, and 10, and run the inner loop for each of those cycle values.

Printing

FOR channel 0:7
   P("Channel: ")
   PL(channel)
   DI(50,channel,1)
   DI(50,channel,0)
ENDFOR

The P (print) and PL (print with a newline) is used to print to wherever the hardware sends text output. This can be useful to help debug programs.

Random

channel = R(0:8)
DI(5, channel, 1.0)
DI(40, channel, 0.0)

The R command creates a random number. It is in the form:

R(<min>:<max>)

where:

<min> is the minimum value that is generated

<max> is one more than the maximum value generated

In this case, R(0:8) produces numbers in the range 0-7 and assigns it to the variable channel.

Overlapping animations

channel = R(0:8)
DI(1, channel, 1.0)
D(40, channel, 0.0)
A(20)

This one is a bit more complicated. The DI command quickly drives one channel all the way on, and then the D command sets up a dim on that channel over 40 ticks. It then animates for only 20 ticks.

That means that after the A(20) finishes executing, the dim on the channel has only halfway finished. It is still active during the next time the code is executed, so it will run in parallel with the next loop.

Larson Scanner

// Larson Scanner
FOR A 0:6
   DI(1,A,1.0)
   D(50,A,0.0)
   A(12)
ENDFOR
FOR A 7:1:-1
   DI(1,A,1.0)
   D(50,A,0.0)
   A(12)
ENDFOR

    

The first line is a comment. The first for loop uses the overlapping technique to slowly dim the different channels from channel 0 to 6, and then the second for loop does the same thing that runs from 7 down to 1.