Author Archives: Rob Reynolds

The End of an Era

via SparkFun: Commerce Blog

If yo're reading this, I’m going to assume you are familiar with Maker Media. You’ve had a subscription to Make Magazine, you’ve been to a Maker Faire or you’ve built a project from one of their many guides. The bottom line is, if you know SparkFun, then it’s a fairly safe bet that you also know Maker Media.

alt text

You may have seen this installation at any of the last three Bay Area Makers Faires.

As I’m sure you’ve heard by now, late Friday night Maker Media announced it had halted operations and laid off its entire staff. Company founder and CEO Dale Dougherty, who has been open about the struggles to keep the company in the black, confirmed the closure to TechCrunch.

While this may not come as a complete surprise, it is still a sharp blow to the maker community – and it truly is a community. I have many friends, colleagues, even occasional acquaintances whom I’ve met through this community. The medium didn’t matter – a welder, a stitcher, a glass blower, and all those makers who it’s difficult to even categorize – they all came together as a community, and not just at Maker Faires and Mini Maker Faires. We've kept in touch on forums and chat rooms, maker spaces and sometimes, despite our typically introverted nature, we would even meet up socially to grab a coffee or beer and share ideas and encouragement.

Make Magazine collection

Some of my Make: Magazine collection. Remember how shocked we were with the new size of volume 37?

I remember the first time I found Make Magazine. It was almost summer, 2005, and as I waited for my car to get its oil changed, I wandered into Barnes & Noble and there it was. The cover boasted of a DIY R2-D2, and in the table of contents, it promised to teach me how to hack my old mouse into a light-seeking robot, and how to use a lens from my SLR camera to create a webcam telescope. I was hooked! I purchased the next two issues that year from a local news stand, and at the beginning of 2006 I started my subscription. As issue after issue continued to roll in over the years, I had great plans to build about 80 percent of the projects I saw, as I read each issue cover to cover. I probably started about 15 percent, and finished about two percent, but I loved every minute of it. Every success and failure, every frustration and new bit of knowledge re-ignited my passion for making, hacking and engineering.

SparkFun RC Plane

Inspired by the RC plane "The Towel" in volume 30, I made this SparkFun-themed variation. (Yes, it actually flies!)

I remember my first Maker Faire. I remember all of the Maker Faires I attended. It was at my third Faire that I bought my first 3D printer, because even though it was still more than I could afford, the discount being offered was so great I couldn’t afford NOT to buy it! I saw amazing projects and met incredible people and was inspired a thousand times over in a thousand different ways! I felt like these were my people, this was where I belonged, and everyone there, whether presenters, exhibitors, or attendees, brought something interesting to the table.

alt text

Attendance had started to level off in recent years, and actually began decreasing at the flagship Faires.

As the above graphic from Maker Media shows, attendance at Maker Faires (and interest in the maker movement, one can assume) has continued to grow. However, attendance at the Bay Area and New York flagship Faires had in recent years leveled off and actually declined, and although there continues to be an increase in interest around the world, interest does not always equate to profitability.

With the plethora of free online content available to makers, and the increasing costs of publishing a printed periodical, subscriptions fell. Additionally, the cost of producing an event the size and scope of the flagship Maker Faires became unrecoupable, with major sponsors like Intel, Microsoft and Disney exiting not only the Maker Faire, but the entire maker market.

What’s next?

Dougherty still believes that even though the company may have failed as a business, it is certainly not failing as a mission. In his interview with TechCrunch, Dougherty said, “We’re trying to keep the servers running. I hope to be able to get control of the assets of the company and restart it. We’re not necessarily going to do everything we did in the past but I’m committed to keeping the print magazine going and the Maker Faire licensing program.” He went on to say, “It works for people but it doesn’t necessarily work as a business today, at least under my oversight.”

While we may very well have seen the last Bay Area Maker Faire, at least in the form with which we’ve grown familiar, it’s important to remember that there have been over 200 owned and licensed Maker Faire events (featured and Mini Maker Faires) per year in over 40 countries. Even if we have, in fact, seen the last of the Bay Area and World Maker Faire New York events, I think it’s safe to say that in one form or another, there will continue to be gatherings of makers. We will still meet up, share ideas, learn from each other and help push each other to new heights, and if Dale Dougherty has his way, they will remain under the Maker Faire flag. Maker Media and Make Magazine have done amazing things for the maker community, and they will forever have my gratitude.

comments | comment feed

Is That Justice Calling?

via SparkFun: Commerce Blog

Last week, when I should have been in the studio filming another new product video, I was instead sitting in a large room with a few hundred other civic-minded individuals, fulfilling my annual jury duty obligation. I was pulled for a jury pool, brought into the courtroom and questioned for what seemed like an exceptionally long time by both the prosecuting and defense attorneys. Finally, in the end I was released, and didn't have to sit on the jury this time around...

...just not in time to crank out this new product video. However, I wasn't about to pass up a chance to demo something as exciting as a battery charger! For all of the information on it, take a look at the product page, as well as the Hookup Guide.

So without further ado, here it is, the new Sparkfun LiPo Charger Plus!

Sorry, Your Honor, but this is the only charge in which I am interested

comments | comment feed

How’s the Weather Up There?

via SparkFun: Commerce Blog

Springtime in the Rockies can bring with it some very interesting weather. So this week, I decided to grab our SparkFun micro:climate kit for micro:bit, put it all together and see exactly what was happening outside our windows here at SparkFun HQ. I've programmed it to record and broadcast temperature, humidity, wind speed, wind direction and rainfall, although that last one takes a bit of patience. And while my desk is right on the edge of the micro:bit's Bluetooth radio range with the interference that the building creates, I can walk over to the breakroom and watch all the information scroll by perfectly on my second micro:bit. And by using the SparkFun OpenLog (included in the kit) to record all of the data to a file every sixty seconds, I can always go back and see what happened out there while I was locked inside our windowless studio.

If you haven't yet worked with the micro:bit, or if you want to start digging into block coding (with Microsoft MakeCode), MicroPython or even JavaScript, this kit will help you gain a mountain of working knowledge, with a very gentle learning curve.

comments | comment feed

Lord of the Ring Lights

via SparkFun: Commerce Blog

I’ve spent some time on these Tuesday blog posts talking about, among other things, color mixing, and analog and digital inputs in Python, using our LumiDrive LED Driver in combination with our RGB LED Rings. Well, I thought it was time to combine them all into one semi-useful project.

I’ve used the LumiDrive, along with one of our 3” LuMini LED Rings, to make a macro ring light.

This is a light ring that attached to the lens of your camera and evenly illuminates your subject with the light coming from the camera’s point of view. It makes it easier to avoid errant shadows when shooting close-ups. Most of them are a ring of LEDs with a diffuser lens, while some will offer an additional blue lens and orange lens, to give the resulting image a cooler or warmer look, respectively.

You can purchase an inexpensive one for thirty or forty bucks, and the results will be pretty much what you would expect from that price range. On the other hand, if you’re serious about your close-up photography, you could pick up something like a Kaiser KR 90 Ring Light for just under four hundred dollars – a little more than I would spend, especially when I know I would enjoy the challenge of creating my own. Now if you know me, you know that I believe that any project worth doing is worth overdoing. So with all of the tools at my disposal, why wouldn’t I make a macro ring light that offered 16.7 million colors?

Red ring

Macro ring lights give even illumination from the camera’s point of view when shooting close-ups.

The idea

The last time I talked about color mixing with LEDs, I dealt primarily with RGB. This time around I’m looking at HSV. There are a couple of reasons for this. The first is simply because I wanted to do a bit more exploration into the HSV color model. The second is a bit more practical. The pins broken out on the LumiDrive consist of a pair of analog and a pair of digital. Had there been three analog pins, I might have just set one to each of the red, green and blue values. However, I figured with what we have available, I could use the analog inputs for both hue and saturation, and use the digital inputs to increase and decrease the value by small increments.

HSV Color Space

The HSV Color Space depicted as a solid cylinder, showing the travel of Hue, Saturation and Value. (Image courtesy of Wikimedia Commons.)

In the HSV color space, the hue travels around the space in 360 degrees. Saturation goes from the outside of the space, which is full saturation, to the center, which is no color saturation at all. This means that, regardless of the hue, if the saturation is all the way down, the color will show as white (assuming that the value is all the way up). The value, then, can be roughly equated with brightness.

The brains

The build is based around the SparkFun LumiDrive LED Driver, paired with the LuMini 3” LED Ring. It contains 60 APA102s, and can be easily controlled with the LumiDrive just by writing a little bit of Python code. I prototyped it all on a breadboard, just to make sure I could actually make it do what I wanted it to.

You may have noticed in my previous post that I soldered female headers into the LumiDrive to make life easier. However, once I moved from prototype to final product, I soldered my potentiometers and momentary buttons directly to the board. Also, to create a more finished look, I used a 3.5mm TRRS jack coming out of the LED poke-home connectors. Then, from the LuMini, I used a TRRS audio connector. I figured that this would make transporting the two parts easier, and put less strain on the connections at the LumiDrive end. To power it all, I’m using a 1Ah Lithium Ion Battery.

Now to make my life easier, I did cut the red (+) wire from the battery and installed a small switch to power the project on and off. I’m sure you’re aware that lithium ion batteries can be twitchy, and by twitchy, I of course mean fire-y and explode-y. So if you’re not comfortable with this, you can certainly adjust the top of the housing to easily allow you to unplug the battery from the LumiDrive when not in use.

The body

The two halves of the body took a bit of experimenting and trial and error. For the ring itself, after a few different ideas including variations on a 3D-printed ring clamp, I decided to use a Lens Adapter Ring for the Cokin CBP400A P-Series Filter Holder. I thought it would be ideal, but I was off by about 2mm.

Ring Adapter

By using a ring adapter like this one, I can easily swap out whatever size I need for whichever different lens I might be using.

I wound up having to notch the outside of the ring adapter, as the mounting holes on the 3-inch LuMini fell right on the edge of the ring, but in the end, it gave stability to the entire thing. I designed and printed the ring body, realized that I had forgotten a place to run the wiring, added a notch and a hole, and reprinted. The second print worked almost perfectly, which may be a new record for me!

To add a little stress relief to the cable, and again create the illusion of a thing that someone might have actually purchased, I added a grommet. I also designed a diffuser for the front of the ring. I first printed it with white ABS, but even at 1mm, it was still too thick for the light to make a difference. I’ll try again with a clear ABS.

Exploded 3D model

The designed parts, modelled in Fusion 360.

For the electronics and battery housing, I made it as compact as I could. I also designed it to have a cold shoe and retaining ring, so that it could lock down onto any DSLR camera. The retaining ring goes down onto the cold shoe adapter first, then the adapter gets screwed to the main housing body.

I always enjoy the challenge of figuring out how to design for 3D printers - what needs to be supported, what can’t be supported, how to create and assemble two separate parts so that they can both be properly supported when being printed – all of that stuff. I added holes for the potentiometers and buttons, along with the on/off switch, and I designed the front so that an opening remained for charging the LiPo battery, as well as reprogramming should the need or desire arise, without having to disassemble the housing. I have to say, I’m quite satisfied with the design.

Putting it all together

I have to admit to having a lot of fun playing with a number of variations, not necessarily because I thought they were all possible implementations for this project, but because I like the challenge of math. Seriously, I knew I wouldn’t need a single potentiometer, whose range is read as 65535 steps, to inversely control both red and blue LEDs so that as one rose from 0 to 255, the other descended from 255 to 0, using only integers throughout the range. Setting green at 10 and controlling red and blue in opposition, this is what I came up with:

inverseColors = (math.trunc((HUEpot.value * 255) / 65535), 10, abs(math.trunc((HUEpot.value * 255) / 65535)-254))

Truncating returns integers, not floats, and using the absolute value returns only a positive integer. But I digress.

The dotstar and fancyLED libraries that Adafruit has created were paramount here, as was our LumiDrive code that Elias put together. While I have used the fastLED library on previous Arduino builds, and there are still a number of things that can be done with addressable LEDs in the Arduino environment that aren’t easily accessible using Python, these libraries and circuitPython were extremely helpful in making this happen. Here is what my final code looks like.

import adafruit_dotstar # The LED library
import adafruit_fancyled.adafruit_fancyled as fancy
import math
import time
import board
import digitalio
from analogio import AnalogIn

# Setting up the board's blue stat LED, mostly for testing
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

# Here we'll define the inputs/values for HSV
SATpot = AnalogIn(board.A3)
HUEpot = AnalogIn(board.A4)
VALval = 0.4 # Set the initial value for Value, since it's button-driven

# Setting up the digital IO pins as input buttons
button8 = digitalio.DigitalInOut(board.D8)
button8.direction = digitalio.Direction.INPUT
button8.pull = digitalio.Pull.UP

button9 = digitalio.DigitalInOut(board.D9)
button9.direction = digitalio.Direction.INPUT
button9.pull = digitalio.Pull.UP

# These two variables should be adjusted to reflect the number of LEDs you have
# and how bright you want them.
num_pixels = 40 #The 3" ring has 60, the 2" ring has 40, the 1" ring has 20
brightness = 0.5 #Set between 0.0 and 1.0, but suggest never running at full brightness
startSequence = 0 # Last minute addition to create startup sequence

# Some standard colors.
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
ORANGE = (255, 40, 0)
GREEN = (0, 255, 0)
TEAL = (0, 255, 120)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
MAGENTA = (255, 0, 20)
WHITE = (255, 255, 255)

# This creates the instance of the DoTStar library.
pixels = adafruit_dotstar.DotStar(board.SCK, board.MOSI,
    num_pixels, brightness= brightness, auto_write=False)

# The travel function takes a color and the time between updating the color. It
# will start at LED one on the strand and fill it with the give color until it
# reaches the maximum number of pixels that are defined as "num_pixels".
def travel(color, wait):
    num_pixels = len(pixels)
    for pos in range(num_pixels):
        pixels[pos] = color
        pixels.show()
        time.sleep(wait)

def slice_rainbow(wait): # Just a little startup color animation

    num_pixels = len(pixels)

    pixels[::6] = [RED] * math.ceil(num_pixels / 6)
    pixels.show()
    time.sleep(wait)
    pixels[1::6] = [ORANGE] * math.ceil((num_pixels - 1) / 6)
    pixels.show()
    time.sleep(wait)
    pixels[2::6] = [YELLOW] * math.ceil((num_pixels -2) / 6)
    pixels.show()
    time.sleep(wait)
    pixels[3::6] = [GREEN] * math.ceil((num_pixels-3) / 6)
    pixels.show()
    time.sleep(wait)
    pixels[4::6] = [BLUE] * math.ceil((num_pixels-4) / 6)
    pixels.show()
    time.sleep(wait)
    pixels[5::6] = [PURPLE] * math.ceil((num_pixels-5) / 6)
    pixels.show()
    time.sleep(wait)

# Here's where the action happens
while True:
    if startSequence == 0: # Startup with a quick color animation
        slice_rainbow(0.2)
        time.sleep(0.1)
        travel(BLACK,0)
        time.sleep(0.5)
    startSequence = 1 # Stops opening sequence from continuing to run

    if not button8.value: # Increases the Value in increments of 0.05
        VALval = round(VALval + 0.05, 2)
        if VALval > 0.8:
            VALval = 0.8 # Limit Value (brightness) to 0.8 to avoid meltdown
        time.sleep(0.05) # Debounce
    elif not button9.value:
        VALval = round(VALval - 0.05, 2)
        if VALval < 0:
            VALval = 0
        time.sleep(0.05) # Debounce

    print ("Value value = ", VALval)
    TRYME = fancy.CHSV(HUEpot.value / 65535, SATpot.value / 65535, VALval)
    packed = TRYME.pack() # Converts HSV into HEX

    pixels.fill(packed) # Sets color to given HEX value
    pixels.show() # Illuminates LEDs

    time.sleep(0.01) # Debounce

The result

3/4 shot

I created a graphic so the user knows about where they are with hue and saturation, and which way the value buttons adjust.

Project on Camera

The diffuser didn’t quite make it in time to go to print, but the results are still quite even. Notice it also works on tiny Star Wars characters.

I have to say, I’m very happy with the end result. Now admittedly, since I am adjusting the value incrementally, I am not able to create the full 16.7 million colors originally advertized. However, it does everything I wanted it to, and does it all quite easily.

It’s very user friendly, although I do see some possible issues and changes to improve or customize it as needed. Perhaps you find a color that is absolutely perfect for your needs. That’s great if you never change from it, but what if you want to recall it later? What if you find half a dozen colors that are perfect for half a dozen different types of shots that you frequently repeat?

Maybe you do a little reprogramming so that the value always remains constant, while you can still adjust the hue and saturation, and you re-purpose the buttons so that one saves and enumerates your favorite colors, and the other recalls them. Or perhaps you want to add more visual interest to your shots by illuminating only one side. Maybe each button controls one half of your LEDs, so you can light left side only, right side only or full illumination. I’d love to hear any ideas, variations or improvements you may have on this, or the builds and ideas that are swirling around in your heads. Let’s face it, we learn this stuff so that we can make cool projects, right?

Gandalf Warhol

Completely alter the feel of your images through the lens by making simple adjustments, without a photo editor. How Warholian!

I’ve put the .STL files, along with the code and the graphic, up on GitHub. If you’re interested, you can find them all here, and the full wish list of parts here and below.

comments | comment feed

More Python with the SparkFun LumiDrive!

via SparkFun: Commerce Blog

Last week, we started poking at SparkFun’s new LumiDrive LED Driver and played a little with the available digital pins broken out on the board. This week, we’ll take a look at the analog pins, play with them as inputs, and see if we can put something together using both digital and analog pins.

If you didn’t catch its release, let me introduce you to the SparkFun LuMini LED Driver. This board boasts a SAMD21G-AU microcontroller, to allow you to run Python, micropython, circuitpython, etc.

LumiDrive

The SparkFun LumiDrive LED Driver. So powerful. So elegant. So red.

Let’s dig into analog input on the LumiDrive. The first thing we need to do is import the proper library. So just as we imported the digitalio library to read and control things connected to the digital pins, this time we’ll need to import the analogio library.

import analogio

# Then we impliment it with this line

analog_in = analogio.AnalogIn(board.A3)

There is another way to do this, which I didn’t discuss last week. Unlike programming in Arduino, with Python (or microPython, or circuitPython) you don’t need to import the entire library. If you are only using a single element from that library, it is also possible to do this:

from analogio import AnalogIn

# Now our sketch knows where AnalogIn came from, so
# we don't need to instruct it where to look. We can just use

analog_in = AnalogIn(board.A3)

High value potentiometers

If you’re coming from the world of Arduino (or C), you would expect the analog value coming in from your potentiometer to be somewhere in the 0-1023 range – and you would be correct. However, things are a little different here. Your analog reading will fall within the range of 0-65535 (assuming 16-bit), so there’s a much higher resolution.

Let’s say we just want to see what value our trim pot is returning. Well first, of course, we need to attach our potentiometer. The hookup to the LumiDrive is simple, and just as you would expect. The outside legs of your potentiometer go to 3.3V and GND, with your center leg going to an analog pin. In this case I’m using A3.

Breadboard setup

Adding digital inputs to D8 and D9, and an analog input to A3

The pin.value call will return the raw data from your potentiometer, so with a simple sketch, we can see what our potentiometer is telling us.

import time
import board
import analogio

analog_in = analogio.AnalogIn(board.A3)

# Just read pin value
def get_pin_value(pin):
    return pin.value

while True:
    print (get_pin_value(analog_in))

time.sleep(0.05)    

Once you’ve saved that sketch to your LumiDrive (as main.py or code.py), you can open up a serial terminal and you should see something like this.

Analog Read

Returned analog values from our potentiometer

Note! Even though Mu has its own serial window, the LumiDrive is not yet recognized by it, and will tell you something like:

Could not find an attached device.

Make sure the device is plugged into this computer.

Blah blah blah other important things.

For the time being, I'm simply using TeraTerm. You should have no problems with TeraTerm, CoolTerm or whatever your favorite terminal program happens to be.

Now you probably notice that the value never gets all the way down to zero. With a resolution of 65535 across its rotation, and the little bit of float, that is to be expected. There are ways to eliminate that, but that’s an issue for a whole different post.

Variations

A potentiometer is a means to an end. That is, the values returned from a potentiometer are generally not as exciting as what we do with those values. Let’s consider a few ideas.

Suppose we want to know the voltage going through our potentiometer. Since we’re on a 3.3V setup, we’ll want to take the returned value from our pot, multiply it by 3.3, and then divide it by the full range of our potentiometer. So it’s not just a stream of numbers flying by, let’s tell the casual observer what we’re showing them. Try this.

import time
import board
import analogio

analog_in = analogio.AnalogIn(board.A3)

# Read pin value, calculate voltage on a 3.3V circuit
def get_voltage(pin):
    return (pin.value * 3.3) / 65535

while True:
    print ("Voltage is ", (get_voltage(analog_in)))

time.sleep(0.05)    

Notice that to print a string and a variable together, you just need to add a comma between the two. Now what if instead of voltage, you want to change your RGB values? Just like with the voltage indicator, you simply need to multiply the pin value by the number of your desired range - in this case 256 - and then divide it by the potentiometer’s resolution, 65535.

import time
import board
import analogio

analog_in = analogio.AnalogIn(board.A3)

# Map pin value to a scale of 0-255
def get_color(pin):
    return (pin.value * 256) / 65535

while True:
    print ("Color value is ", (get_color(analog_in)))

time.sleep(0.05)

Floating

Returning a value in the range of 0-255. But come on, your LED isn’t interested in (0, 255, 87.6239).

Of course, this gives us a float, when we really just want an integer. There are a few different ways to do this, and if you have a favorite, or you think my way is terrible, please let me know! For this example, I’m going to use the truncate command from the math library. Remember, we can either import the entire math library, of just the part we need.

import time
import board
import analogio
from math import trunc

analog_in = analogio.AnalogIn(board.A3)

# Map pin value to a scale of 0-255
def get_color(pin):  # Returning only integers this time
    return trunc((pin.value * 256) / 65535 ) #If we used 'import math', this would need to be return math.trunc()

while True:
    print ("Color value is ", (get_color(analog_in)))

time.sleep(0.05)

Integer

Ah, now that’s more like it. Integers as far as the eye can see.

Off the screen and into the wild

We’ve only seen our values returned to us in our serial terminal window. Let’s try to make something happen that doesn’t need a computer screen to show results.

With our three digital inputs from last week, and our analog input from this week, let’s try this. Since I just added components to last week’s breadboard, I still have a yellow momentary button on D8, and a green momentary button on D9, and I’m just adding a potentiometer on A3. We’ll use D6 as well, since it’s there. I want to create a circuit that does the following:

If I push the green button, the LuMini 2-inch ring lights up green; push the yellow button, and the ring lights up yellow; push the on-board button, and the ring will light up red. I’m pretty sure that’s close to what we did last week. But I also want it to blink, and I want to be able to control the speed of the blink with my potentiometer. Let’s try this:

import adafruit_dotstar # The LED library
import math
import time
import board
import digitalio
import analogio

# Setting up the board's blue stat LED to blink
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

analog_in = analogio.AnalogIn(board.A3)

#Setting up the board's onboard button
button6 = digitalio.DigitalInOut(board.D6)
button6.direction = digitalio.Direction.INPUT
button6.pull = digitalio.Pull.UP

# Setting up the digital IO pins as input buttons
button8 = digitalio.DigitalInOut(board.D8)
button8.direction = digitalio.Direction.INPUT
button8.pull = digitalio.Pull.UP

button9 = digitalio.DigitalInOut(board.D9)
button9.direction = digitalio.Direction.INPUT
button9.pull = digitalio.Pull.UP


# These two variables should be adjusted to reflect the number of LEDs you have
# and how bright you want them.
num_pixels = 40 #The 3" ring has 60, the 2" ring has 40, the 1" ring has 20
brightness = 0.25 #Set between 0.0 and 1.0, but suggest never running at full brightness


# Some standard colors.
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
ORANGE = (255, 40, 0)
GREEN = (0, 255, 0)
TEAL = (0, 255, 120)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
MAGENTA = (255, 0, 20)
WHITE = (255, 255, 255)

# This creates the instance of the DoTStar library.
pixels = adafruit_dotstar.DotStar(board.SCK, board.MOSI,
    num_pixels, brightness= brightness, auto_write=False)

# This function takes a color and a delay and fills the entire strand with that color.
# The delay is given in the case you use multiple color fills in a row.
def color_fill(color, wait):
    pixels.fill(color)
    pixels.show()
    time.sleep(wait)

# Here's where the action happens
while True:
    if not button8.value:
        led.value = True
        color_fill(YELLOW,0)
    elif not button9.value:
        led.value = True
        color_fill(GREEN,0)
    elif not button6.value:
        led.value = False
        color_fill(RED,0)
    else:
        led.value = False
        color_fill(BLACK,0)

    time.sleep((analog_in.value * 2) / 65535) #delay is determined by potentiometer reading

    color_fill(BLACK, 0)

    time.sleep((analog_in.value * 2) / 65535) #delay is determined by potentiometer reading

You’ll notice a couple of things in this last example. The first is that while we defined functions to get our analog values in all of the previous examples, we did not in this one. To read our time.sleep variable, we read the analog_in.value within our while True loop. I did this because Python can’t convert a function to a float, and the time.sleep call expects a float as its argument. Second, in getting the value for our time.sleep, I’ve multiplied our analog_in value by two before dividing it by 65535. This sets our maximum sleep time at two seconds, just like multiplying by 3.3 in the earlier example gave us our voltage range.

Wait, there’s more!

But not this week. Again, this is mostly geared toward those among us (myself included) who haven’t done much Python programming as it applies to the physical world. I’m sure there are better or more efficient ways to do exactly what I’ve just done, and since as long as I’m still breathing, I’m still learning, I’d love to see your suggestions and improvements in the comments.

I also recall saying last week that if all went well, I would try to use all of this in some sort of practical application or project. Well, things went well, but not quite that well. I have a head full of ideas as to how to use all of this in a build, but this week there just wasn’t enough time, so that will be a post for another day.

comments | comment feed

Python and the SparkFun LumiDrive

via SparkFun: Commerce Blog

This past week, we released our new LumiDrive LED Driver board. This board should please a lot of folks who have been asking for an LED controller that could run Python or one of its board-based variants. This is made possible because the LumiDriver has an onboard SAMD21 which, unlike the ATMega328P, has the speed, power and size to run Python. So now that we have it, what can we do with it? I’m going to offer a very simple introduction for those out there who are unfamiliar with, or at least very new to, the Python environment. Let’s get started.

alt text

This could be the start of something big. ~Steve Allen

Let’s start at the very beginning

Since we’re starting out simply, let’s take a look at one of the simpler Python editors - Mu. If you’re already completely comfortable with Python, this editor will probably seem restrictive, as it limits itself to only the most essential features. Of course, if you’re already comfortable with Python, you’re probably already using Visual Studio Code, EMACS, VIM, Code Writer or any other more powerful code editor, and doing much more than will be covered here today. From the Code with Mu page, click the download button and you’ll be brought to the download page. Download and install the version appropriate for your system. When you open the program, you’ll be greeted with the coding window.

Mu screen

While Mu may look simplistic, it offers all you will need to start learning the very basics of Python programming, and getting your project lit!

The first thing you may notice is the large, simplistic looking buttons across the top, and the lack of dropdown menus above that. This is by design. Remember, Mu is geared towards those just starting out with Python, so its aim is to allow people to simply focus on the very basics without overwhelming them with all of the options, features and possibilities they will eventually need.

Vim screen

While Vim is a great editor, its “movie hacker breaking into a secure server” look can be intimidating to anyone just starting out. (Image via slant.co.)

First look

If you plug in your SparkFun LumiDrive LED Driver, you should see a new window open, named CircuitPy. You can now write your code directly to the LumiDrive itself. Your LumiDrive came with a few necessary files pre-loaded, included one called main.py. This is where you’ll find the code your LumiDrive will run (note: You could also use code.py, as well as main.txt or code.txt. These files will be automatically detected, and the board will run them as soon as they’re detected, but it will only run one. A quick test here seems to indicate that it chooses the one that is first alphabetically). If you click on the Load button at the top of Mu, you can navigate to your CircuitPy drive, open main.py and edit it directly.

Now you may look at the 174 lines of code, then glance back at the board doing nothing more than blinking its on-board blue LED on and off every half second, and think to yourself, that is a whole lot of code to be doing a whole lot of not much. But if we look closer, we can see that most of that code is a series of functions. In Python, a function is a block of code that only runs when it is called. Each function is defined using def.

def a_small_function():
    print("My, what a lovely function")


while True:
    a_small_function()

In this instance, while True: acts just like void loop() does in your Arduino code. Knowing that, we can trim down the main.py code to just a handful of lines, and get the same output.

import time
import board
import digitalio

# Setting up the board's blue stat LED to blink
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

#Ah trusty ol' blink. 
while True: 
    led.value = True
    time.sleep(.5)
    led.value = False
    time.sleep(.5)

Taking control

So let’s say you don’t want the on-board LED to blink mindlessly on its own, but you want to take control of it. Let’s use the on-board button to accomplish this. The button on board the LumiDrive is attached to D6, so we’ll need to establish this in our code in the same way we established the on-board LED. We define its pin number, its direction, and in the case of our button, that it is pulled HIGH so that it isn’t floating.

import time
import board
import digitalio

# Setting up the board's blue stat LED to blink
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

#Setting up the board's onboard button
button = digitalio.DigitalInOut(board.D6)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP

# Here's whare the action happens 
while True: 
    if button.value:
        led.value = False
    else:
        led.value = True

    time.sleep(0.01) #debounce delay

alt text

< click > Light goes on. < click >Light goes off. < click > Light goes on. < click >Light goes off.

I’ve written the if: else: code like this to show you what’s going on, but Python allows for a simpler way to accomplish this. Since we’re really just saying, “When the button is pulled HIGH, the LED should be LOW; while the button is pressed LOW, the LED should be HIGH,” we can simplify the code, like this:

while True:
    led.value = not button.value  #LED state is always opposite button state

    time.sleep(0.01) #debounce delay

Moving off the board

Now let’s expand beyond the on board, and branch out. Since the LumiDrive pairs perfectly with our new LuMini LED Rings, we’ll add one of those. The Hookup Guide will get you soldered up and give you a great start, and we’re going to use some of that code here, but rather than let it go on its own, we’ll add button control.

Just like we did with the on-board LED and button, we’ll need to give the board all the information it needs on the LuMini Ring - the library we’ll be using, how many LEDs there are, how bright we want them, etc. All of this was in the initial main.py file that came on your LumiDrive board, but if you’ve overwritten it while experimenting here, you can always grab it from our GitHub repo. Remember the functions we talked about earlier? Well here’s where they come into play. For the sake of simplicity and ease of reading, I’m only going take one of the functions, but we’ll have to make some declarations so the code knows what is expected of it. Let’s go with the very basic color_fill function and a couple of standard colors as we define them in the code.

import adafruit_dotstar # The LED library
import math
import time
import board
import digitalio

# Setting up the board's blue stat LED to blink
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

#Setting up the board's onboard button
button = digitalio.DigitalInOut(board.D6)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP

# These two variables should be adjusted to reflect the number of LEDs you have
# and how bright you want them.
num_pixels = 60
brightness = 0.25

# This creates the instance of the DoTStar library. 
pixels = adafruit_dotstar.DotStar(board.SCK, board.MOSI, 
    num_pixels, brightness=brightness, auto_write=False)

# Some standard colors. 
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
ORANGE = (255, 40, 0)
GREEN = (0, 255, 0)
TEAL = (0, 255, 120)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
MAGENTA = (255, 0, 20)
WHITE = (255, 255, 255)

# This function takes a color and a delay and fills the entire strand with that color. 
# The delay is given in the case you use multiple color fills in a row.  
def color_fill(color, wait):
    pixels.fill(color)
    pixels.show()
    time.sleep(wait)

# Here's whare the action happens 
while True: 
    if button.value:
        led.value = False  #I've kept the onboard LED involved for testing purposes
        color_fill(BLACK,0)
    else:
        led.value = True
        color_fill(BLUE,0)

    time.sleep(0.01) #debounce delay

I said we would be using a couple of the standard colors as we’ve defined them, but the LED ring just turns on one color, and then turns off. That’s because the second color (or first, depending on how you look at it) is BLACK, which we’ve defined as (0, 0, 0). And if you want to get crazy, you could define any color in this way. If you do a Google search for “rgb color picker,” you’ll be presented with a really nice tool that will allow you to chose any color, and it will return not only the RGB value, but also HEX, HSV, HSL and CMYK. Want to add a rich purple to your palette? In your color definitions, just add PURPLE = (133, 6, 224), and you can now call that color just like the others.

Color picker

With the slider for hue, and the circle for saturation and value, you can easily get the values for RGB, HSV, or whatever format you’re using in your code.

Multiple options

With multiple digital pins, why not use multiple buttons? It’s really simple, thanks to Pythons elif command. As I’m sure you can figure out, this is just a shortening of what would be else if in Arduino code. The elif statement allows you to check multiple expressions for TRUE and execute a block of code as soon as one of the conditions evaluates to TRUE. So let’s whip up a quick circuit, and throw a little code together. Attach a pair of momentary buttons to D8 and D9 (and to GND). I’ve soldered female headers to my LumiDrive to make prototyping easier, but if you’re sure you want a momentary button on each of the two digital IO pins, you can just solder straight to the board. The code is changed slightly, to set up the two digital IO pins. I’ve also added another one of the functions from the initial demo, just to make it a little more interesting.

import adafruit_dotstar # The LED library
import math
import time
import board
import digitalio

# Setting up the board's blue stat LED to blink
led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

#Setting up the board's onboard button
button6 = digitalio.DigitalInOut(board.D6)
button6.direction = digitalio.Direction.INPUT
button6.pull = digitalio.Pull.UP

# Setting up the digital IO pins as input buttons
button8 = digitalio.DigitalInOut(board.D8)
button8.direction = digitalio.Direction.INPUT
button8.pull = digitalio.Pull.UP

button9 = digitalio.DigitalInOut(board.D9)
button9.direction = digitalio.Direction.INPUT
button9.pull = digitalio.Pull.UP

# These two variables should be adjusted to reflect the number of LEDs you have
# and how bright you want them.
num_pixels = 40 #The 3" ring has 60, the 2" ring has 40, the 1" ring has 20
brightness = 0.25

# This creates the instance of the DoTStar library.
pixels = adafruit_dotstar.DotStar(board.SCK, board.MOSI,
num_pixels, brightness=brightness, auto_write=False)

# Some standard colors.
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
ORANGE = (255, 40, 0)
GREEN = (0, 255, 0)
TEAL = (0, 255, 120)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
MAGENTA = (255, 0, 20)
WHITE = (255, 255, 255)
PURPLE = (133, 6, 224)  # We added this from the RGB Color Picker, remember?!

# This function takes a color and a dely and fills the entire strand with that color.
# The delay is given in the case you use multiple color fills in a row.
def color_fill(color, wait):
    pixels.fill(color)
    pixels.show()
    time.sleep(wait)

# The travel function takes a color and the time between updating the color. It
# will start at LED one on the strand and fill it with the give color until it
# reaches the maximum number of pixels that are defined as "num_pixels".
def travel(color, wait):
    num_pixels = len(pixels)
    for pos in range(num_pixels):
        pixels[pos] = color 
        pixels.show() 
        time.sleep(wait)

# Here's where the action happens
while True:
    if not button8.value:
        led.value = True
        travel(YELLOW,0)
    elif not button9.value:
        led.value = True
        travel(GREEN,0)
    elif not button6.value:
        led.value = False
        travel(PURPLE,0)
    else:
        led.value = False
        color_fill(BLACK,0)

    time.sleep(0.01) #debounce delay

What next?

This should give you a decent start adding a little input and gaining a little control to your LumiDrive board. Play around, experiment and remember it’s always a good idea to back up your code so you can always return to a working point if things go terribly sideways. Next week, we’ll look at adding analog input and then, if all goes well, put it all together into a practical build of some sort.

comments | comment feed