Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Much of the boat traffic on the Hudson consists of barges shuttling bulk commodities between the Atlantic and the Port of Albany. I think this is a crude oil barge, based on the Christmas Tree plumbing that was more visible when it passed under the Mid Hudson Bridge:
Walkway and Barge – from Mid Hudson Bridge
We crossed the Walkway Over the Hudson westbound, where a work crew was tending a crane. That’s how they do repair and inspection:
Walkway Inspection Crane – from Mid Hudson Bridge
The Hudson River has far fewer power boats than in years gone by, probably due to their gallon-per-minute fuel consumption:
Power boat on Hudson River – from Mid Hudson Bridge
Because the current control loop closes through the Arduino loop(), the code’s path length limits the bandwidth. Worse, the PWM filter imposes a delay while the DC value catches up with the new duty cycle. Here’s what that looks like:
LoopStatus ILED 50 mA div – 200 50 150 25 mA
The setpoint current for this pulse is 200 mA, ramping upward from 50 mA. It should have started from 25 mA, but the loop really wasn’t under control here.
The top trace goes low during the drain current measurement, which occurs just before the code nudges the gate drive by 1 PWM count to reduce the error between the setpoint and the measurement. A delay(1) after each PWM change, plus the inherent delay due to all the program statements, produces an update every 1.7 ms, more or less.
Even at that low rate, the current overshoots by 50 mA before the loop can tamp it down again. The current varies by 200 mA for 7 PWM counts, call it 30 mA per count at the high end, so overshooting by 50 mA comes with the territory. There’s just not a lot of resolution available.
The program reads each pulse duration and amplitude from an array-of-structs, so it’s a simple matter of software to save the gate drive voltage at the end of each pulse and restore it when that pulse comes around on the guitar again:
if (millis() >= (EventStart + (unsigned long)Events[EventIndex].duration)) {
Events[EventIndex].drive_a = VGateDriveA; // save drive voltages
Events[EventIndex].drive_b = VGateDriveB;
if (++EventIndex > MAX_EVENT_INDEX) // step to next event
EventIndex = 0;
VGateDriveA = Events[EventIndex].drive_a; // restore previous drives
VGateDriveB = Events[EventIndex].drive_b;
SetPWMVoltage(PIN_SET_VGATE_A,VGateDriveA);
SetPWMVoltage(PIN_SET_VGATE_B,VGateDriveB);
delay(PWM_Settle);
digitalWrite(PIN_ENABLE_A,Events[EventIndex].en_a); // enable gates for new state
digitalWrite(PIN_ENABLE_B,Events[EventIndex].en_b);
NeedHallNull = !(Events[EventIndex].en_a || Events[EventIndex].en_b); // null sensor if all off
EventStart = millis(); // record start time
}
… which produces this happy result, with a different time scale to show all four pulses in the array:
I Sense Amp ILED 50 mA div – 200 100 150 50 mA
The top trace shows the current amp output that goes into the Arduino analog input and the bottom trace shows the MOSFET drain current. Notice those nice, crisp edges with a nearly complete lack of current adjustment.
The small bumps in the amp output just after the LED turns off happen while the the code nulls the Hall effect sensor offset. Whenever the LEDs turn off, the code nulls the sensor, which is probably excessive; it really doesn’t have much else to do, so why not?
This trickery doesn’t improve the loop bandwidth at all, because the code must still drag the current to meet each setpoint, but now that happens only when the pulse first appears. After a few blinks, the current stabilizes at the setpoint and the loop need handle only slight variations due to temperature or battery voltage changes.
Speaking of voltages:
VDS ILED 50 mA div – 200 100 150 50 mA
The top trace now shows the MOSFET drain voltage and the bottom still has the LED current. There’s only 650 mV of difference at the drain for currents of 50 mA and 200 mA through the LEDs, with about 1 V of headroom remaining at 200 mA.
The power supply delivers 7.4 V to the anode end of the LEDs, so they drop 6.3 V @ 200 mA and 5.7 V @ 50 mA. Some informal knob twiddling suggests that the MOSFET loses control authority at about 6.5 V, so, given that there’s not much energy in the battery below 7.0 V anyway, the program could limit the maximum current to 50 mA when the battery hits 7 V, regain 650 mV of headroom, and run at reduced brightness (and perhaps a different blink pattern) until the battery drops to 6.5 V, at which point the lights go out.
There’s more improvement to be had in the code, but those pulses look much better.
(If you’re keeping track, as I generally don’t, this is Post Number 2048: love those round numbers!)
The rise and fall times for the LEDs on the over-the-top blinky taillight left a bit to be desired, at least if you were interested in short pulses:
VG 1193 mV – ID 50 mA-div – 1 ms PWM filter – overview
So I spliced an analog switch between the PWM filter and the gate, with inputs selecting ground and the PWM drive voltage:
Switched MOSFET gate – analog switch schematic
This being a prototype, that involved epoxying a SOT23-6 package atop the MOSFET, with flying wires all over the PCB:
Hall Effect PCB – MOSFET gate switch
It works pretty well:
VDS ILED 50 mA div – 200 50 150 25 mA
The top trace is the drain voltage and the bottom trace is the LED current at 50 mA/div.
The current transitions come out vertical at any sweep speed, even through the Tek Hall effect current probe. The pulses would be rectangular if I weren’t changing the current for each one.
The current feedback loop closes through the Arduino’s main loop, which includes a 1 ms delay after each PWM output change before measuring the LED current. As a result, each loop takes just under 2 ms, but, fortunately, ramping from 25 mA (in the last pulse) to 200 mA (in the first pulse) requires less than 10 PWM increments; you can see the stairsteps if you squint.
Next up: bump the PWM to 64 kHz (from 32 kHz) and rip out the pull-down resistor that used to hold the gate near ground when the output floated as an input. That should improve the output ripple caused by the MOSFET’s bias as a perfectly serviceable linear amplifier.
The control loop now fetches durations & currents from an array-of-structs, so I can customize each pulse. An obvious enhancement: remember the gate drive that produced a given current, then restore the corresponding value as the starting PWM setting for each pulse, so the loop begins closer to reality. The gate drive varies with temperature and suchlike, so it can’t be a compile-time constant, but maybe the startup routine could preload the array / list / cache with values taken from a “lamp test”.
Four close-together flashes repeating at about 2 Hz, even with two runt pulses, turn the LEDs into a real eye magnet…
Mary heard a faint sound from the back of her bike that neither she nor I could track down. Standing in the garage, we decided it was slightly louder when the wheel turned backwards, but the sound didn’t correlate with anything.
Eventually, I held my hand over the wheel while turning it, whereupon the problem made itself obvious:
The short hook on the right side embedded itself the in the tread, with the rest sticking out. Turning the wheel backwards dragged the longer arc on the fender, making a slightly louder sound. Of course, the tightest fender-to-tire clearance occurs just behind the seat, where it isn’t easily visible.
We planned to ride west on the Walkway and return east on the Mid-Hudson bridge, but encountered an obstruction in mid-span:
Maintenance Crane on Walkway Over the Hudson
Pedestrians and cyclists on diamond-frame (a.k.a., “wedgie”) bikes could sneak past the outrigger legs on the south (left) side of the crane, although that’s surely a Bad Idea for worksite safety. Our big ‘bents wouldn’t fit through, so we just turned around and enjoyed the ride home; a good time was had by all.
While tweaking that picture, I noticed a speck of dirt on the monitor that, upon closer investigation, turned out to be a hidden object:
Maintenance Crane on Walkway Over the Hudson – airplane
Use a video player to find the interesting section, then bracket it with the starting time and duration. Putting the -ss starting time before the -i input file lets the decoder skip through the file, rather than grinding through everything preceding the specified frames.
The -q 1 setting wrings the best quality out of the input video file. That’s why the camera captures 1920×1080 video @ 60 fps; I wish I could dial its compression back a bit, but that’s not an option.
So.
Do you think he didn’t quite kill me between bites or is that a K-Mart bag and he was yakking on a phone like everybody else?
Near Miss – Jackson Drive – 2014-05-03 – car interior
Clicky for more dots. I compressed the image from the avconv output file, but it’s good enough.