UX and Product Design
IMG_7592.jpeg

Blog

Thinking out loud

Posts in Work Notes
Trouble: shot, or how I learned to stop worrying and love AC
FullSizeRender.jpg

The short version: everyone was right. Induction caused massive AC on the sensor data wire.

IMAGE.JPG

I added a makeshift low-pass power filter with a resistor and capacitor I had handy in my tool bin, and it solved the sensor issue.

Turns out, 25 meters of narrow gauge wire is a terrific antenna.

I need to do a proper measurement of the AC frequency on the power rail and design an RC filter to match it for the production design, but this is good enough for now.

Sketchbook page: Witch Lights Harness

Trace paper is where I always start

At World Maker Faire last Sunday, an absolute highlight was being able to see Laura Kampf and Sophy Wong talk about design and making.

I sat with Alex Glow, and was totally thrilled with the presentation. I was especially happy when they showed their notebooks, because I, too, had documenting and sharing my sketchbook drilled into me in design school.

So that's why I'm so happy that I was able to find this photo of the most critical sketch for the Witch Lights, the paper copy of which is in one of about 6 boxes in my closet. This is where I started at one end of a roll of trace paper, and just outlined all the chain of components and elements I'd identified so far. That’s where I realized I could create a unified harness and LED structure with 3D printing.

When I started the sketch, I was considering using polypropylene chinese food containers for the junction boxes. They're recyclable, tough, water-tight, and inexpensive. But my experiment with a hole-saw and the conduit fittings had proved frustrating and laborious.

The conduit I had was 3 times the diameter of what I'd thought I was ordering at 2am the other week. But I liked the way I could make it form shapes in mid-air, and so here I was trying to lay out how using it would change the design.

Note on the left there’s an actual fitting for the harness. I had just found a solidworks model of these online, so I knew I could use my Solidworks assembly context skills to very quickly design hardware to fit it.

At the time I was thinking of printing lids for Chinese take-out containers.

In the upper left, you can see I started a marker sketch of a take-out housing, and then sketched a simpler, streamlined housing that allows the diameter of the conduit to say roughly the same all through the chain.

In the lower right: I figured out I could put sensors in the housings too, reducing another complexity

And you can also see the cable glands and 4-pin waterproof cables for the NeoPixels, which I had already figured out at this point.

It's so cool to find a moment where you pulled it all together and made a design decision that worked out. I'm excited about this!

So yeah... this is basically how I draw. It's not the best. But it gets the idea across I hope.

Next up: building an enchanted notebook so I have shots like this of all my in process work. Because I’ve lost another goddamn notebook.

Back from Firefly 2018
IMG_3682.jpg

I went to Firefly 2018 as DevilBunny, the Mad Which of Firefly.

And now I'm home, exhausted and sick after the hardest Firefly I've ever had in five years.

Show above: The Which's Familiar, Samhain, on the bin containing Witch Lights 2.

I have stories, pictures, and video, which I plan to release as video podcasts on this feed, featuring video of the Witch Lights test setup in my back hard at home, and my voice as I tune the animation, and tell stories of this and past Fireflies.

If there's any interest, I'll have friends on to tell their own stories.

But until then, I'm recuperating. Suffice it to say: despite major difficulties, the Witch Lights were in place and running in "Burn Night" celebration mode at the hour the bug burned. Which counts as making charrette at the last possible moment.

And that's only one thing among a week of an intensity I haven't known in... possibly ever? Certainly never without some form of accompanying trauma.

Anyway. I'll be back. But resting has to happen.

Magical Mystery Crash

I've spent several hours in the last couple days trying to debug a weird crash in the Witch Lights when writing to global variables.

The TL;DR of it is, depending on various factors, swhen I define some, but not all global variables, and then try to read or write to those globals, the Arduino freezes up after the test pattern reaches the end of the LED strip.

If you're interested in the summary of what I'm doing next to try and fix it, scroll to the bottom.

I can work around the issue by either commenting out the specific globals in question (and any code that references them), or by commenting out other globals that get loaded into RAM, such as pre-rendered raster animations. Which, the first thing I thought--in fact, the first thing anyone who I talk to about this thinks--is that I've run out of memory somehow. This kind of thing is exactly what happens when you're up at the limit of your available SRAM. That is where I began looking.

The following is a log of Arduino Memory Kremlinology, where I attempt to interpret the behavior of RAM on an Arduino Due by checking who stands next to Stalin in the May Day Parade.

Here is a memory map of the Arduino Due:

0x0008 0000 - 0x000B FFFF   256 KiB flash bank 0
    0x000C 0000 - 0x000F FFFF   256 KiB flash bank 1
                                Both banks above provide 512 KiB of contiguous flash memory
    0x2000 0000 - 0x2000 FFFF   64 KiB SRAM0
    0x2007 0000 - 0x2007 FFFF   64 KiB mirrored SRAM0, so that it's consecutive with SRAM1
    0x2008 0000 - 0x2008 7FFF   32 KiB SRAM1
    0x2010 0000 - 0x2010 107F   4224 bytes of NAND flash controller buffer

One key takeaway is that the Due has a contiguous address space, despite having separate 64K and 32K banks. That address space ranges from 0x2007 0000 to 0x2008 7FFF. I was under the impression that this was not the case, so that's good to know.

Because the Due is basically a weird experiment that escaped into the wild, the usual Arduino instructions for viewing available RAM don't work. Fortunately, I found instructions here. The memory report code is looking at the contiguous RAM address space I just mentioned, like so:

char *ramstart=(char *)0x20070000;
    char *ramend=(char *)0x20088000;

The code calculates 4 things:

  • Dynamic RAM used (the "heap", which grows from the "top" of the static area, "up")
  • Static RAM used (globals and static variables, in a reserved space "under" the heap)
  • Stack RAM used (local variables, interrupts, function calls are stored here, starting at the "top" of the SRAM address space and growing "down" towards the heap; when functions complete, their local variables and pointers are cleaned up, and the stack shrinks)
  • "Guess at free mem" (which is complicated)

The "free mem" calculation is stack_ptr - heapend + mi.fordblks

Which, in theory, is subtracting the totaly amount of unallocated memory blocks in the range below the stack? I think? I'm not sure. I'm reading the internet and interpreting.

Here's the memory report during the setup() function:

Dynamic ram used: 0
    Program static ram used 7404
    Stack ram used 80

    My guess at free mem: 90820

OK, so total free RAM is reporting at roughly 88K out of 96K, not bad.

Dynamic ram used: 1188
    Program static ram used 7404
    Stack ram used 104

    My guess at free mem: 94492

    Loop Count: 0

Now, at this point we're executing the main loop() function for the first time, and here we see something interesting. The total amount of RAM used and the guess at free RAM no longer add up. What gives?

Well, what happens here is that memory in the heap, the dynamic RAM reported up top, has been freed up, but because more stuff is sitting on "top" of it in memory address space, the memory isn't really actually free to be used. The C function that reads a memory space and reports back on free blocks doesn't know that, however. So that's why we can't really rely that much on the "free mem" guess.

The numbers to watch are Dynamic RAM (the "heap") and the Stack, which are memory addresses on opposite sides of the big contiguous memory space. Generally, when you run into a memory issue on an Arduino, it's because you've been writing new stuff onto the end of the heap, and it collides with the stack. That's not happening here.

Dynamic ram used: 1380
    Program static ram used 7404
    Stack ram used 104

    My guess at free mem: 94300

    Loop Count: 1

During loop() running the test pattern, memory usage stays totally static:

Dynamic ram used: 1380
    Program static ram used 7404
    Stack ram used 104

    My guess at free mem: 94300

    Loop Count: 738

    Dynamic ram used: 1380
    Program static ram used 7404
    Stack ram used 104

    My guess at free mem: 94300

    Loop Count: 739

Serial stops responding on loop cycle 741, each time (I've run several tests):

Dynamic ram used: 1380
    Program static ram used 7404
    Stack ram used 104

    My guess at free mem: 94300

    Loop Count: 740

So that's loop number 740. On 741, the contents of memory change:

Dynamic ram used: 1348
    Program static ram used 7404
    Stack ram used 104

    My guess at free mem: 94332

Three things jump out at me.

  • Dynamic RAM (the "heap") dropped from 1380 to 1348, a difference of 32 bytes
  • The free memory estimate has changed from 94300 to 94332, which basically mirrors the change in the heap
  • It might be crashing when it tries to access counter, which is a global int?

I have previously had a crash just like this, when I was trying to access a global int array for the noIdle feature. I found that moving the value I was trying to access into the FaerieSprite class definition fixed the crash, and wrote it off as a scope issue to be debugged later. But what if this was the cause instead?

While I was trying to debug noIdle, I used the debug(n) function to light up LEDs during various stages of various method calls, to see where the crash occurred. What I found at the time was, the foremost test pattern sprite failed when currentPixel == NUM_LEDS - 1, after its Update() method called MarkDone() successfully. When that happens, the isDone bool is set to TRUE, and SpriteManager deletes that sprite's Sprite pointer from the spriteVector pointer array.

Which seems to have happened, given the 32 bytes freed up from the heap. All righty, then.

So, here's a hypothesis: SpriteManager deletes a sprite, and then the exact moment we next try to access the counter global int, boom. We crash.

The problem is, if so, finding the root cause of this is hard.

So I'm not going to.

Instead, I'm going to take the booleans out of globals entirely, and change them into functions, which will query the mode pin when they are called. Memory used by functions is stored in the stack, completely on the other side of the memory address space from where we're allocating and deleting sprites from memory.

That's the plan, anyway. I'll report back when I run the first experiments.

Faeries go both ways

This is a recap of all the work I did over the weekend. In theory, this stuff goes before the last journal entry. But I'm posting it now. For reasons.

DimTrail() function

Last year, we were seriously concerned about the performance of the Arduino while animating 4-20 sprites at a time, and so we pre-rendered as much as possible as a precaution. As it turns out, we're nowhere near taxing the performance of the processor: our bottleneck for performance is FastLED::Show.

So this year, instead of a pre-rendered pixel trail for the faerie sprites, I added a DimTrail() method.

When I move the pattern CRGB sprite one pixel fowards, it doesn't turn off the LED that, in the previous interval, was the rear-most pixel of the pattern. So I call DimTrail() on that pixel, and it gets faded by ~50% using a FastLED function. The DimTrail() method is recursive, so it then steps back one pixel and fades again, and so on, until it detects that it's reached a black pixel, at which point it exits.

// add int direction and make tailpixel += direction, and when direction is "backwards", offset the dimtrail by 3 pixels to prevent dimming the sprite
    void DimTrail(int tailPixel, int dimFactor, int direction) {
        if (tailPixel < 0) return;
        if (tailPixel > NUM_LEDS) return;
        if (! leds[tailPixel]) return;

        leds[tailPixel].fadeToBlackBy(dimFactor);
        tailPixel += direction;
        DimTrail(tailPixel, dimFactor, direction);
    }

The FastLED fadeToBlackBy function operates in 1/256ths, meaning that if you feed it 128, it fades 50%, if you feed it 64, it fades 25%, and so on. So I also wrote a function that maps updateInterval (the variable framerate) to dimFactor, so that now when faerie sprites move slowly, they start with a short trail, and when they accelerate to move quickly, they stretch out a long trail behind them.

That's great, but when the sprites stop for their idle animation, the trail fade doesn't look right.

So I created a FadeOutTrail() method, which itself calls DimTrail() recursively.

void FadeOutTrail(int tailPixel, int dimFactor, int direction) {
    if (tailPixel < 0) return;
    if (tailPixel > NUM_LEDS) return;
    if (! leds[tailPixel]) return;

    // Recursively run DimTrail() at tailPixel+1, so trail fades from the dim end
    DimTrail(tailPixel, dimFactor, direction);
    FadeOutTrail(tailPixel + direction, dimFactor, direction);
}

The result of this method is that, instead of staying the same length and fading out awkwardly, when the sprites stop and idle, the trails shrink from the far end inwards. It's a subtle improvement, but a nice one.

Algorithmic braking

Now that I've done some pixel animation of how I want the faerie sprites to move, I've worked out that I like to have them slow down and stop over a range of about 4-8 pixels, with the trails fading as they slow. I'm now working on slowing the sprites down by manipulating the framerate (which is how they accelerate).

I just put in some movement logic where the sprites calculate their distance from their destination pixel, and when that is within a certain percentage of the overall travel distance, it starts braking.

However, currently deceleration is handled by a single factor; how many milliseconds do you add or subtract to the updateInterval on each call of updateTravel()?

For acceleration from stop, it's simple enough to start at about 40ms, and subtract 1ms per interval as you travel. The result is a reasonably natural enough acceleration towards "cruising speed".

For making the sprite slow down to a stop, however, that doesn't really result in a pleasing deceleration curve.

After some work, I came up with this acceleration method:

int AccelerateTravel() {
    // Decide whether braking or accelerating
    if (currentDistance < totalTravelDistance * brakePercentage) {
        if (!isBraking) {
            isBraking = true;
            brakeDistance = abs(currentDistance - totalTravelDistance);
            brakePixel = currentPixel;
        }
    } else {
        isBraking = false;
    }
    // Accelerate or brake by returning positive or negative values to subtract from updateInterval
    if (! isBraking) {
        int x = abs(currentDistance - totalTravelDistance);
        return round(sqrt(x) * accelerationFactor);
    } else if (isBraking) {
        int x = abs(currentPixel - brakePixel);
        return -1 * round(sqrt(x) * brakeFactor);
    }
}

Whether braking or accelerating, the change to updateInterval is a function of the distance traveled from the point where you started accelerating. accelerationFactor and brakeFactor are semi-randomized values assigned on sprite creation, whose purpose is to make sure that, even if two sprites start at the same pixel headed in the same direction, they'll have slightly different acceleration and brake speeds, so they'll move a bit differently from each other. I'm still dialing in what those values should be.

Algorithmic idle animation

After the faerie sprite in the above video slows to a stop, it idles across 3 pixels, back and forth. I sketched this in terms of pixel values from the colorPalette array in my new notebook, and my friend Scott Longely translated it into a set of simple algorithms for each pixel.

That isn't the full desired result, though. What I need to do next is to write a colorFade() function to transition smoothly between pixel values.

I've found example code as gists on GitHub. Both use FastLED functions for the fade, which have so far resulted in good animation performance, so I'm game to see how they go.

I have so far not looked at the code at all to see how suitable it may be. So that'll happen soon.

Transition from idle to travel

There's a visual discontinuity at the moment where the animation transitions from isIdling to false; at that moment, it chooses a new destination, and draws the travel sprite. Unfortunately, the last frame of the idle animation and the travel sprite do not match perfectly.

So one of my next action items is to fix that.

Faeries go both ways

Last year's build of the Witch Lights concealed a dirty secret: sprites only had the capacity to move in one direction.

Basically, a sprite started at 0, and when in travel mode, it moved by adding 1 to CurrentPixel. When CurrentPixel was > NUM_LEDS, the sprite was marked done. (Or it started at NUM_LEDS and traveled towards 0.)

What I want, though, is for faeries to make one long travel move in the "forwards" direction (whichever way that happens to be for this particular sprite), and then make a bunch of small, random moves in either direction, basically flitting about while waiting for people to catch up along the path.

So that means that all the movement logic needed to be examined and re-written. Which I will not get into here. But basically, it happened.

So that's working, too.

Reverse the polarity of the neutron flow

The Witch Lights have two long-range motion sensors, one at each end. When someone on the "far" end triggers a sensor, we have to spawn a faerie sprite that starts at NUM_LEDS (the far end), and travels towards pixel 0.

There are lots of details involved in making sprites travel in the "other" direction; for example, when transitioning from travel mode to idle, the idle pixel animation has to run in reverse. And the logic to check whether the sprite is "done" (and can be deleted from memory) has to know the sprite's direction, and so on.

Or.

When we run an Update() method, when we take what we've animated and send it to the LEDs, what we actually do is call a function called stripcpy. That function takes the CRGB struct that we've been manipulating, looks up currentPixel, and then uses memcpy to write our pixels to the leds CRGB struct.

OK. So.

What if we told stripcpy to count from the other direction, and write the CRGB data backwards?

In theory, you'd get a perfect mirror image of the animation you're running. The sprite logic would always run from pixel 0 to NUM_LEDS, but it would draw to the pixels as though it was running in the other direction.

That simplifies the requirement for direction-checking logic all through the various methods of the sprite class, and only requires that sprites be created with a drawDirection value, which then gets passed to all stripcpy calls.

That's the theory. I haven't tried it out yet. But I'm eager to try it. If it works, that's a huge simplification, reducing the number of potential logic bugs in Travel mode.

It would very slightly complicate collision detection, but I'm pretty sure that's easily solved.

Up Next

Here's the todo list:

  • Add "flit" behavior, so that faeries make long travel moves towards NUM_LEDS, then a number of short, random moves in either direction, with short idles, and then move on with another long travel move
  • Make stripcpy run backwards
  • Add jumper-pin modes (set some pins to auto-pullup, and then jumper them to ground to activate various modes for setup and documentation)
  • Collision detection
  • Adjust accelerationFactor and brakeFactor randomization, maybe make them values passed to sprites on creation?
  • Create subclasses of the current sprite and change the idle animation
  • Fix the visual glitch when transitioning from idle to travel modes (FadeColor?)
  • Define zones of pixels where faerie sprites should not stop and idle
    • areas where the trail coils around a tree
    • zones at the very beginning and end of pixel strips
  • Use collision detection to make faeries more likely to stop and flit around near other faeries
  • Double-check memory usage and possible memory leaks caused by my messing with things I do not sufficiently understand

And other things. Secret things.