Fade Language Tutorial

Fade is a simple line-oriented language designed to make it easy to write animations.

Breathe

The simplest program will cause a single LED to breathe - to dim to full brightness and then dim back to off.

ConfigLed(0, "RGB", 1, 15)

Di(25, 0, {1, 1, 1})
Di(25, 0, {0, 0, 0})

The ConfigLed() statement tells the system what LEDs are attached.

This one says: "Configure group zero (the first group of LEDs) as an RGB (WS2812) addressable LED strip with 1 LED connected to pin 15 on the ESP. "

Then there are two simple Di function calls.

The Fade system always knows the current color of all of the LEDs, so the code that we write only needs to define three things:

  1. Which LED to change
  2. What the target color is for that LED
  3. How long to take to change the LED.

"25" is the cycle count for the operation. Fade runs on a 10 millisecond clock, updating 100 times per second. 25 cycles is therefore 25/100 seconds, or 1/4 of a second. 250 milliseconds if you prefer that measurement.

"0" is the LED that we want this operation to apply to .

"{1, 1, 1}" is the target color. Variables in Fade can be single numbers or a series of numbers - you can think of them either as "arrays" or "vectors". This example shows the syntax that is used to create a three-number series from individual numbers. Because we are using this in the Di(), these three numbers specify the red, green, and blue color components. Color components run from 0 (all the way off) to 1 (all the way) on. So {0, 1, 0} would be full green, and {0.5, 0.5, 0.0} would be a somewhat dimmed yellow.

When this statement executes, you can think of it as doing something like this:

Set color to {0.04, 0.04, 0.04}
wait 10 milliseconds
Set color to {0.08, 0.08, 0.08}
wait 10 milliseconds
etc.

During this command, the LED will fade from full back to full white.

The second Di command does opposite dim, from full white down to full black.

At this point, the end of the program is reached, and Fade will start over again from the beginning.

Moving Breathe

ConfigLed(0, "RGB", 10, 15)

for led 0:9

  Di(25, led, {1, 1, 1})
  Di(25, led, {0, 0, 0})

endfor

This is a simple modification of the last program; it configurates a 10 LED strip and then runs the same animation on the leds in sequence. This is an introduction to "for" loops in Fade.

Bouncing Breathe

ConfigLed(0, "RGB", 10, 15)

led = 0
increment = 1

while (1)
  Di(25, led, {1, 1, 1})
  Di(25, led, {0, 0, 0})

  led = led + increment

  if (led == 0 || led == 9)
    increment = -increment
  endif

endwhile

This iteration modifies the code so that the LED will bounce back and forth.

It uses an outer while loop. This while loop will run forever, but that's okay in this case.

The increment value is used to move the animation to the right or to the left, and it flips in direction at each end.

This code introduces the if and endif statements.

Overlapped Bounce

The example looks a little like the Larson Scanner:

 

 

What we are missing is the fading part of the animation. We can add that with a very small change to our code:
ConfigLed(0, "RGB", 10, 15)

led = 0
increment = 1

while (1)

  D(50, led, {0, 0, 0})

  led = led + increment

  if (led == 0 || led == 9)
    increment = -increment
  endif

  D(10, led, {1, 1, 1})
  A(10)

endwhile

The previous examples all used the Di() function, which says "set a target color and wait until the system gets to that target color".

This version uses the D() function, which says "set a target color", and leaves the animation to another statement.

The first D() statement takes whatever the current led is and starts a fade to black on it over 50 cycles, or half of a second. The second D() statement sets up an overlapping fade for the next led to bright, but only over 10 seconds.

And then the A() - animate - statement performs the animation for 10 cycles. That's enough to turn the new led on, but it's not long enough for old led to fade all the way to black - that will take 5 cycles through the loop.

The brightness looks something like this:

Cycle Count Led 0 Led 1 Led 2 Led 3 Led 4
0 0 0 0 0 0
10 1 0 0 0 0
20 0.8 1 0 0 0
30 0.6 0.8 1 0 0
40 0.4 0.6 0.8 1 0

This ability to overlap operations is at the heart of most animation effects.

Random Colors

It would be nice to show a color other than white.

ConfigLed(0, "RGB", 10, 15)

func GetColor()
  red = Rand()
  green = Rand()
  blue = Rand()

  max = Max(red, green, blue)
  factor = 1.0 / max

  return {red * factor, green * factor, blue * factor}
endfunc

ledCurrent = 1
increment = 1
color = GetColor()

while (1)
  D(50, ledCurrent, {0, 0, 0})
  ledCurrent = ledCurrent + increment
  if (ledCurrent == 0 || ledCurrent == 10)
    increment = -increment
    color = GetColor()
  endif

  D(10, ledCurrent, color)
  A(10)
endwhile

The GetColor() function calls the Rand() built-in function to get a random number between 0.0 and 1.0. This works fine, but the colors tend to be a little dim as they might be something like "{0.5, 0.3, 0.1}". The code figure out max and factor scales those numbers up so that the biggest one is 1.0.

The rest of the code is mainly unchanged.

A real-life project example

This is the code from a real-life project:

ConfigLed(0, "RGB", 1, 27)
ConfigLed(1, "PWM", 4, 17, 16, 4, 2)
ConfigLed(2, "PWM", 1, 14)
ConfigButton(0, "TOUCH", 15, 20)
LampAutoShutoffTimeInSeconds = 10 * 100 // 10 seconds

D_GasGenerator = 0
D_Green = 4
D_White = 3
D_Fan = 5


if ReadButton(0) == 1

  Pl("ON")
  D(100, D_Fan, 0.95)

  D(100, D_GasGenerator, {1, 1, 0.2})
  A(100)

  Di(5, D_Green, 0.8)
  Di(5, D_Green, 0)
  Di(100, D_White, 0.8)

  for wait 0:LampAutoShutoffTimeInSeconds
    if ReadButton(0) == 1
      break
    endif
    A(1)
  endfor

  Pl("OFF")

  D(100, D_White, 0)
  D(50, D_GasGenerator, {0, 0, 0})
  D(1, D_Fan, 0)
  A(100)
endif

A(1)

This example is quite a bit more complicated...

It sets up three LED groups.

  1. A single WS2812 LED on pin 27
  2. A group of 4 PWM LEDs on pins 17, 16, 4, and 2
  3. A simple PWM LED on pin 14

The ESP-32 can drive up to 16 separate PWM outputs in hardware. In this case, the group with 4 PWM outputs is connected to a custom 4-channel LED controller that drives an RGBW LED. The single output PWM group is actually used to control a cooling fan.

After the LEDs are configured, a button is configured. The ESP-32 supports 10 touch inputs in hardware; the pin is simply wired to some sort of metallic contact and detects when somebody touches that contact. In this example, the touch input is pin 15, and 20 is the touch threshold; any touch value below 20 will trigger the button. Buttons can also be triggered externally through the web interface.

Until the button is pressed, the A(1) animate block executes and nothing happens.

When the button is pressed, the fan is turned on and the single WS2812 RGB LED is turned on, then the green channel of the big LED is flashed, and the white channel is ramped up.

The code then enters a timing loop; if the user presses the touch button again the loop exits, or it turns off on its own after a given number of seconds (set to 10 seconds for the demo).

At that point, the the channels are turned off, the loop is exited, and the code waits for the next button press.

In addition to touch buttons, the Fade system also supports physical pushbuttons.

This code use the PL() - or "print line" - functionality; when a P() or Pl() statement is executed, it sends its output over the serial channel so it can be displayed by a desktop computer or laptop that is connected to it. This is very useful for debugging.

Here is a video of this project in operation.