Author Archives: Owen Lyke

Enginursday: Switching Sensors with a WS2811 Addressable LED Driver

via SparkFun: Commerce Blog

Sometimes we sign up to do crazy things - like going skydiving with your best friend or running a marathon in flip flops. In this case I wound up hatching a plan to use 192 of the same I2C sensor in an interactive installation. As the sheer size and complexity of the goal sank in I grew increasingly curious if there was a simple way to wire all those sensors together. The result is elegant in theory, noisy in reality, and probably won't work at scale. However it is really fun to talk about so stick around to hear about using addressable LED drivers as software switches!

A Collaborative Staff

The task: create an interactive installation that allows young children to play with sound and color collaboratively. When the Museum of Boulder asked me and Joe Ryan create this we started thinking big! Joe's fantastic idea was to create a larger-than-life musical staff that kids could place notes on to craft their very own song. The color of the notes would determine what kind of instrument was used. After settling on a size (four measures, four positions per measure, and 12 notes per position) we needed to find a way to actually make it work.

Color Nodes

Sensing colors has been made easy by ICs such as the ISL29125. The general idea was to use one of these at every note position along with an addressable LED that could be used to both illuminate the notes for a reading and serve as a visual guide for getting started. A central system would take readings to determine which notes should be played and by which instruments. The challenge was to connect 192 I2C devices that all use the same address.

There are several ways to accomplish this task - perhaps using a tree of I2C multiplexers, using several independent sensing groups each with its own microcontroller, and even extravagant methods using wireless communication. I went in search of a solution that would minimize cost and installation complexity. What I found was the WS2811 addressable LED driver IC.

WS2811-Based Switch Design

Knowing that LED data would need to be sent to every node gave me an idea - what if the power to the color sensor could be controlled in the same way? Perhaps this would allow all the sensors to share one I2C bus, as long as only one was powered at a time. In retrospect it is hard to remember which search terms exactly led me to what I needed but eventually I found the datasheet for the WS2811. A companion to the popular WS2812B addressable led, the WS2811 encapsulates the same driver but instead breaks out the R, G and B output pins so that you can attach your own LEDs.

The datasheet indicated that the outputs were constant-current drains of up to 18.5 mA. Not ideal - a controllable voltage output would have been simpler to use but fortunately we know how to convert current to voltage and vice-versa.

V = I • R
I = 18.5 mA
V = 3.3 V (using ISL29125 supply voltage)
R = V / I = (3.3 / 0.0185) = 178.37 Ω

A 178.37 ohm resistor with 18.5 mA current flowing through it will drop 3.3 V. In other words a constant current driver set to pull 18.5 mA from 3.3 V through a 178.37 ohm resistor would need to set its output at 0V!

Now it's not quite that simple, unfortunately. It would be if the LED driver was a true "analog" constant current driver, but LED drivers typically use Pulse Width Modulation (PWM) to approximate constant current. The duty cycle of the PWM signal is what we perceive as the brightness of the LED. When trying to use an LED driver as a switch you run into a problem - your switch changes quite frequently, even when you have it set to just one value!

PWM Cuty Cycle Diagram
pwm duty cycle diagram
Image: Android Developer's Site

You might hope that asking the LED for either a 0% or 100% duty cycle would work well enough, but we can't guarantee how the driver operates. In the case of the WS2811 I found that a channel value of 0 results in a true 100% duty cycle (this makes sense for a common anode LED driver), but the max value (255) only approaches a 6% duty cycle.

The ability to reach a true 100% duty cycle is good for powering the sensor because it means minimal noise. The fact that the supply voltage will occasionally come up, however, is concerning because that may cause any one of the sensors to turn on and interfere with communication on the I2C bus. In order to combat this I used a low-pass RC filter that incorporates the current-limiting resistor.

The WS2811 uses a PWM frequency of 2 kHz, so a 6% pulse would have a width of (6 / 100) • (1 / 2000) = 30 μs. A 30 μs square wave pulse contains many frequency components, the slowest of which is (1 / 30 μs) = 33.3 kHz. In order to block that frequency one would choose a corner frequency below that value. Using a simulated circuit on falstad.com/circuit, I played around with various capacitor values to get a good balance of high-frequency blocking (higher capacitor values) and the time it takes to power on a sensor (low capacitor values)

falstad circuit simulator for RC filter on LED driver output

falstad.com/circuit simulation of WS2811 switch

fc = 1 / (2 • π • R • C)
R = 220 Ω (choose a common resistor value greater than 170.37 Ω)
C = 1 μF
fc = 1 / (2 • π • 220 • 1 • 10-6) = 723 Hz

Using the design parameters from above I laid out a prototype schematic and board in Eagle.

circuit schematic in Eagle PCB

Testing

In an effort to move quickly I ordered PCBs and the WS2811 driver ICs at the same time. In fact my original design used a slightly more complicated circuit that I was able to simplify during testing. We've been discussing the simpified circuit but the boards contain a few extra components and a host of white-wire fixes. That's a lesson in why you should always do a 1:1 scale package check for any new parts that you intend on using! Below is an image of the prototype as tested:

prototype v01 with white wire fixes and simplified circuit

To test the board I wrote a quick test sketch. It uses the FastLED library to control the LED data line (for the WS2811 as well as the WS2812B illumiator LED), and the SparkFun ISL29125 Arduino library to read from the RGB sensor.

/******************************************************************************
Color_Node_Test.ino

Test sketch for for turning on/off an ISL29125 RGB sensor using a WS2811 LED 
driver IC. 

Owen Lyke 
25 Mar 2020

Requires:
SparkFun_ISL29125_Arduino_Library
FastLED library by Daniel Garcia
ESP32 based microcontroller such as the SparkFun ESP32 Thing Plus (https://www.sparkfun.com/products/15663)

Setup:
- Build your own color node based on the schematics shown at: https://www.sparkfun.com/news/3266
- Connect the I2C pins SCL and SDA to the Wire port of your ESP32 based board
- Connect the LED data line to pin 18 of the ESP32 on your board
- Connect GND and 5V lines

This code is beerware.
Distributed as-is; no warranty is given. 
******************************************************************************/

/********************/
/* USER SETUP BEGIN */

#define DISPLAY_TITLES 1      // 1 to print field names to serial
#define DISPLAY_RGB 0           // 1 to print r g b values to serial
#define DISPLAY_HUE 1           // 1 to print computed h value to serial
#define DISPLAY_COLOR 1         // 1 to print nearest color to serial

// Define a type to hold detectable colors
typedef struct _color_t {
  float hue;
  const char* name;
}color_t;

// Here's the list of colors that the system can name on its own (you may add your own based on hue angle [0.0, 360] )
color_t colors[] = {
  {0.0, "Red"},
  {60.0, "Yellow"},
  {120.0, "Green"},
  {180.0, "Cyan"},
  {240.0, "Blue"},
  {300.0, "Magenta"},
};

/* USER SETUP END */
/******************/

// Includes
#include <math.h>
#include <Wire.h>
#include <FastLED.h>          // Click here to get the library: http://librarymanager/All#FastLED_Daniel_Gracia
#include "SparkFunISL29125.h" // Click here to get the library: http://librarymanager/All#SparkFun_ISl29125

// RGB sensor control
SFE_ISL29125 RGB_sensor;
unsigned int red = 0;
unsigned int green = 0;
unsigned int blue = 0;

// WS2811 / WS2812B control
#define DATA_PIN 18
#define NUM_LEDS 2    // Each color node has two 'leds' - one WS2811 to control the sensor power and one WS2812B for illumintation
CRGB leds[NUM_LEDS];

void setup() {
  Serial.begin(115200);                                     // Start Serial 

  FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);

  sensorPower(true);
  FastLED.show();
  delay(1000);

  while(!RGB_sensor.init()){                                // Initialize the ISL29125 with simple configuration so it starts sampling
    Serial.println("trying to start the sensor!");
    delay(50);
  }
  Serial.println("Sensor Initialization Successful\n\r");
}

void loop() {
  /* Begin Taking a Reading */

  sensorPower(true);                // Turn on the sensor  
  setLED(200, 200, 255);            // Turn on the LED to illuminate the subject area
  FastLED.show();                   // ^- adjust the balance of white here... blue seems to need some help (higher Vf than other leds..)

  delay(2);                         // some time for sensor to come online
  RGB_sensor.init();                // now perform initialization since the sensor had been asleep

  delay(200); // sensor continuously runs ADC at ~ 10 hz so to be sure wait 0.2 seconds before reading

  delay(300); // delay to combat voltage sag from turning on all the leds...
              // I've experimentally determined that while there is no LED brightness that completely 
              // eliminates noise in detected color there is a minimum total delay between turning on
              // the leds and taking a sample that gets darn close. Its approx 500 ms total (including
              // time dedicated to letting the sensor read)

              // the final product may as well turn on all the leds, wait half a second, and then sample
              // all of the color sensors rapidly. 


  red = RGB_sensor.readRed();     // Sample the sensor
  green = RGB_sensor.readGreen();
  blue = RGB_sensor.readBlue();


  sensorPower(false);             // Turn off the sensor
  setLED(0, 0, 0);                // Turn off the LED
  FastLED.show();                 // Apply changes to the 'LED' data (includes WS2811 'switch' changes)

                                  // Now let's try to sample the sensor to show that it has really been shut down
  delay(1);                       // Time for the sensor VDD line to fall to 0
  RGB_sensor.init();
  RGB_sensor.readRed();
  RGB_sensor.readGreen();
  RGB_sensor.readBlue();

  printResults();                 // Show the results on the Serial monitor

  delay(200);                     // delay 200 ms before taking another reading
}

void sensorPower(bool on){
  LEDS.setBrightness(255);                                  // Ensure full brightness so that WS2811 controls are not scaled

  if(on){
    leds[0] = CRGB(0, 0, 0);                                // Turn on the sensor by writing a 0 value to the R channel
  }else{
    leds[0] = CRGB(255, 0, 0);                              // Turn off the sensor by writing 255 to the red channel
  }
}

void setLED(uint8_t R, uint8_t G, uint8_t B){
  leds[1] = CRGB(R, G, B);                                  // The LED is the second item in the 'led' array (the first is the WS2811 sensor switch)
}



/*********************/
/* UTILITY FUNCTIONS */

float max3( float Rp, float Gp, float Bp, uint8_t* index ){
  // hacky way to find maximum of three (not tested well or even well-thought-through)...
  float Cmax = 0.0;
   if(Rp >= Gp){
      if(Rp >= Bp){
        Cmax = Rp;
        *index = 0;
      }
    }
    if(Gp >= Bp){
      if(Gp >= Rp){
        Cmax = Gp;
        *index = 1;
      }
    }
    if(Bp >= Gp){
      if(Bp >= Rp){
        Cmax = Bp;
        *index = 2;
      }
    }
    return Cmax;
}

float min3( float Rp, float Gp, float Bp, uint8_t* index ){
  // hacky way to find minimum of three (not tested well or even well-thought-through)...
  float Cmin = 0.0;
   if(Rp <= Gp){
      if(Rp <= Bp){
        Cmin = Rp;
        *index = 0;
      }
    }
    if(Gp <= Bp){
      if(Gp <= Rp){
        Cmin = Gp;
        *index = 1;
      }
    }
    if(Bp <= Gp){
      if(Bp <= Rp){
        Cmin = Bp;
        *index = 2;
      }
    }
    return Cmin;
}

void printResults( void ){
    float Rp = (float)red/255;
    float Gp = (float)green/255;
    float Bp = (float)blue/255;

    uint8_t max_ind = 0;
    uint8_t min_ind = 0;
    float Cmax = max3(Rp, Gp, Bp, &max_ind);
    float Cmin = min3(Rp, Gp, Bp, &min_ind);
    float delta = Cmax - Cmin;

    float hue = 0.0;
    if(Cmax == 0.0){
      hue = 0.0;
    }else{
      switch(max_ind){
        case 0: hue = 60 * (fmod(((Gp-Bp)/delta), 6.0)); break;
        case 1: hue = 60 * (((Bp-Rp)/delta) + 2); break;
        case 2: hue = 60 * (((Rp-Gp)/delta) + 4); break;
        default: break;
      }
    }

    // search list of colors for the closest one 
    //  (todo: when overall lux levels are low just say nothing is there)
    const char* identified_color = NULL;
    float prev_diff = 360.0;                              // start with a high (impossibly so) difference
    float diff = 0.0;
    uint8_t num_colors = sizeof(colors)/sizeof(color_t);
    for(uint8_t indi = 0; indi < num_colors; indi++){     // loop through all the named colors
      diff = abs(hue - colors[indi].hue);                     // find the difference between the selected color hue and the calculated hue
      if ( diff >= 180.0 ){                                   // correct for differences over 180 degrees because the spectrum is circular
        diff = 360.0 - diff;
      }
      if( diff < prev_diff ){                                 // if this difference is smaller change the detected color to this name
        prev_diff = diff;
        identified_color = colors[indi].name;
      }
    }

#if DISPLAY_RGB
#if DISPLAY_TITLES
    Serial.print("B: "); 
#endif
    Serial.print(blue);
    Serial.print(" ");

#if DISPLAY_TITLES
    Serial.print("R: "); 
#endif
    Serial.print(red);
    Serial.print(" ");

#if DISPLAY_TITLES
    Serial.print("G: "); 
#endif
    Serial.print(green);
    Serial.print(" ");
#endif

#if DISPLAY_HUE
#if DISPLAY_TITLES
    Serial.print("Hue: ");
#endif
    Serial.print(hue);
    Serial.print(" ");
#endif

#if DISPLAY_COLOR
#if DISPLAY_TITLES
    Serial.print("Color: ");
#endif
    Serial.print(identified_color);
    Serial.print(" ");
#endif

    Serial.println();
}

Using a digital logic analyzer I was able to record some great data. You can see that the rise time of the sensor VDD line matches what is expected from the simulation, as well as the same small ripples when the sensor is turned off. You can also see that the sensor responds to I2C transactions while it is powered. Shortly after sending the 'off' command, the VDD line falls and the sensor no longer responds to I2C transactions. This suggests that turning off unused sensors should be enough to allow using more than one sensor on the same I2C bus.

digital logic analzer trace of sensor VDD line and I2C transactions

Further Considerations

This project is a work in progress. To scale this single test up to an array of 192 nodes will present more challenges such as:

  • Sample speed - the test above does not leave any time for the sensor to make ADC conversions, so the readings come out as 0. In reality each sensor will need to be on for 100 to 200 ms to get a good sample.
  • I2C Bus Capacitance - generally it is challenging to use I2C over long distances because of the open-drain topology.

These challenges will likely preclude the use of this method in the Rainbow Forest project. Still, using the WS2811 in creative ways could like this can be useful in a host of projects that already involve addressable LEDs such as model train layouts, DIY LED shows with special effects, and much more!

If you use the WS2811 in a project of your own let us know!

comments | comment feed

Enginursday: Exploring the Embedded Startup Process

via SparkFun: Commerce Blog

Artemis, my close friend, has urged me to learn much more about the bare-metal details of getting code running and supporting the familiar features that we often take for granted in C++. It is actually a very interesting topic and a beneficial thing to understand when working on embedded systems. Sadly, however, it is a fairly challenging topic to find information about on the internet - and what you can find is daunting, to say the least.

Since we are not fans of reinventing the wheel we've developed some examples you can follow along with using an Artemis board. After doing so you will know:

  1. How the linker organizes compiled code
  2. How startup code initializes variables
  3. How the stack and heap are managed
  4. How global/static object constructors are conveniently called before you get to main()

Check out the examples, explanation and instructions on the embedded-startup exploration repo!

Here are some useful tools to have along the way:

Artemis Boards

SparkFun RedBoard Artemis Nano

SparkFun RedBoard Artemis Nano

DEV-15443
$14.95
SparkFun RedBoard Artemis

SparkFun RedBoard Artemis

DEV-15444
$19.95
SparkFun Artemis Module - Low Power Machine Learning BLE Cortex-M4F

SparkFun Artemis Module - Low Power Machine Learning BLE Cortex-M4F

WRL-15484
$8.95

SparkFun Artemis SnowBoard

DEV-15839

Debuggers

J-Link EDU Mini Programmer

J-Link EDU Mini Programmer

PGM-15345
$18.00
1
J-Link EDU Base Programmer

J-Link EDU Base Programmer

PGM-15346
$60.00
J-Link BASE Compact Programmer

J-Link BASE Compact Programmer

PGM-15347
$378.00

Accessories

SparkFun Serial Basic Breakout - CH340C and USB-C

SparkFun Serial Basic Breakout - CH340C and USB-C

DEV-15096
$8.95
3
USB 2.0 Cable A to C - 3 Foot

USB 2.0 Cable A to C - 3 Foot

CAB-15092
$3.95

comments | comment feed

Enginursday: Playing with CRCs in Python

via SparkFun: Commerce Blog

Recently, the engineering team has needed to implement Cyclic Redundancy Checks (CRCs) for several different projects. The algorithm is easy enough to copy from the internet and forget, but my curiosity just couldn't quit there! CRCs have a very fascinating mathematical underpinning that relates to information theory, computer hardware and more. Trying to get a better understanding of CRCs eventually led me to discover the fantastic legendary ASCII text file called 'crc_v3.txt`. Dr. Ross Williams, the author, once hosted the file at http://www.ross.net, however it can't be found there any more 😔.

Don't fret - everyone knows nothing ever really disappears on the internet, and this is no exception. A work of art and a beacon for curious minds 'crc_v3.txt' will live on forever. You can go pay it a visit here.

Alone 'crc_v3.txt' is a great read, but there are still a few points that might be hard to follow. To satiate my curiosity I created a follow-along Python script to demonstrate the math. You can check it out on GitHub CRC_Exploration or by trying it out live in this post, thanks to the REPL.it widget below. Just click the green 'run' arrow and peruse the output, then try changing the code yourself!

comments | comment feed

Enginursday: 3D Volumetric Display with Transparent OLED

via SparkFun: Commerce Blog

Not surprisingly, this all started when (perhaps jokingly) a clever colleague questioned, "Why don't you stack 'em up like a sub sandwich?" A merely mundane Monday morning quickly became a maniacal midnight mash-up for yours truly, mad scientist. A little planning and proving the power of this particular display was providential in the proceeding process.

Sub Sandwich GIF of ten stacked OLED displays

Initially it was imperative to test the inconceivably incredible display's transparency. With only one unit working wonderfully well, nine more non-functioning displays were stacked on top, one by one. When at last the image at the bottom faded from view, the battle was won but the total number was ten.

Breadboard view of the Super Speedy SPI Bus

Another astonishing achievement that empowered this futuristic artifact was the super speed of the SPI bus. Said bus, in coordination with ten tiny chip select lines, was capable of continually captivating the consciousness of the (possibly concerned) crowd in a blistering burst of brilliance.

3d image of the emerald moving around

Nevertheless, such an exceedingly embryonic idea would be irrefutably evanescent could an elegant software solution not be embraced. Since we can spy furthest standing upon the shoulders of cyclops, I silently sought a singular font of pseudocode. Upon applying Bresengham's arcane craft to an additional dimension, one artful enigma appeared.

Stacked OLEDS on breadboard, showing our Elegant Emerald

Moral of the Story

So what's the take away from this experience? Several things:

  • There's a very cool 3D graphics library for Arduino out there by M Rule that allows users to render STL files on a display. If you want to look further, you can also see the code as one of the examples for the transparent graphical OLED.
  • SPI is a very nice protocol when you've gotta go fast. The symmetry is also pretty nice, as you can see from the stack of boards that all share MOSI, SCLK, D/C, VCC and GND. The only wire that couldn't be shared was the CS line, which I protected from the other layers with a smidgin of kapton tape.
  • Drawing in 3D seems hard, right? Well it's actually easier (from a number-crunching perspective) than drawing something 3D on a 2D screen. When going to 2D, you also need to project your points and determine if there are any overlapping parts.
  • If you like algorithms you should check out Bresenham's line algorith applied to 3D.

comments | comment feed

Have You Seen HyperDisplay?

via SparkFun: Commerce Blog

What Is HyperDisplay?

alt text

HyperDisplay is perhaps the greatest graphics library I have ever written! And, erm, yeah I've toootally written like a lot of graphics libraries in my day...

Jokes aside, HyperDisplay is a graphics library that makes it easy to support and use new displays, and offers flexibility to users without unnecessary overhead. There are a lot of good embedded system graphics libraries out there, from the feature packed LittlevGL to the ever-popular Adafruit_GFX, but none of these libraries fit the bill of exactly what we wanted: a capable yet uncomplicated library that could easily be extended by SparkFun employees and customers alike.

Why Is HyperDisplay?

So, with that plug out of the way let's get to the personal side of the story, the part you won't hear about in the official documentation.

This summer I started working at SparkX alongside Nate, Nick, Jim and the amazing Ciara Jekel. Nate had visions of releasing a whole lot of cool new display-related technology, from RGB OLEDs to ePaper displays and touchscreen panels. Ciara and I both got started on our own products in a more or less isolated fashion - since reading datasheets is hardly a social activity. As is SparkFun fashion we also developed Arduino libraries to support our newborn products, and boy was that a process. I looked to past SparkFun products for inspiration, and also had a good time learning about some famous graphics algorithms like Bresenham's Line and the Midpoint Circle.

At some point Ciara and I started talking about our work and we realized that not only had we doubled our efforts, but we had also made it harder for users to learn our software because of the slight differences in naming, parameter arguments and even how various shapes are defined. Ciara observed that her ePaper and my RGB OLED were the same up to the point of deciding a color and passing actual signals to the device. It seemed like a no-brainer to pool our efforts and develop HyperDisplay.

How we designed HyperDisplay just wouldn't fit here -- it is worthy of its own whole discussion -- but I want to say thank you to Ciara for her invaluable input during the process. It was very rewarding to work as a team.

How to Learn HyperDisplay

A good place to start learning HyperDisplay is the tutorial that we've just released:

Everything You Should Know About HyperDisplay

There you can learn about how to use the drawing functions, easily make repeating patterns, draw in windows, and even allocate memory for persistent storage.

P.S. What's Next for HyperDisplay?

Try it out for real! HyperDisplay is for you. Though we will support our new display products, the really exciting prospect is that any person can support a new display and the possibilities are endless! I'd really like to see a display that draws in sand - so I'll leave that as a challenge to the reader. Most importantly, go start something!

comments | comment feed

Why We Made a U.FL Tutorial

via SparkFun: Commerce Blog

Technology is always changing (though not necessarily improving) and today’s silicone rubber may one day be thought of the way we see Bakelite plastic now. Of course the same is true for electronics - transistor radio kits from the late 50’s can’t hold a match to the complexity of electronics that hobbyists have access to now. One thing that hasn’t changed is the gap between the ability to handle small tolerances between hobbyists and industrial component consumers. It was the case when Raytheon produced the CK722 for the hobby market, and it’s still true, as evidenced by the funky way I had to hold my antenna during the ProRF 1W range test to keep the U.FL connector from popping off.

alt text

See, we have an interesting relationship with the latest and greatest stuff. On one hand, using super small components makes a satisfyingly compact board with plenty of shiny, not to mention that you can fit more inside an enclosure. On the other hand, soldering a 0.5mm pitch FPC can challenge even an experienced soldering tech.

The U.FL connector is one of those items that saves a lot of board space but definitely requires a little extra effort to take care of and use in a project. Not only is it easy to accidentally disconnect, but it can also be damaged by using it incorrectly. Since we want your RF gear to last through as many projects as possible, we wrote a tutorial called Three Quick Tips About Using U.FL. It will help illuminate why and how we take care of our U.FL connections.

Since technology isn’t showing any signs of slowing down it’s up to us hobbyists to improvise, adapt and overcome!

New!

Three Quick Tips About Using U.FL

December 28, 2018

Quick tips regarding how to connect, protect, and disconnect U.FL connectors

comments | comment feed