The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Software

General-purpose computers doing something specific

  • Twiddling Linux Swap Performance

    Depending on a solid model’s complexity, OpenSCAD will sometimes chew through system memory, consume the entire swap file, and then fall over dead. In an attempt to work around that situation, I recently jammed a 32 GB USB drive into the back of the box, turned it into a swap device, and then told the kernel to back off its enthusiasm for swapping.

    Format the USB drive as a swap device:

    sudo mkswap /dev/sd??   #--- unmount it before you do this!
    Setting up swapspace version 1, size = 31265292 KiB
    no label, UUID=0f559a8c-67b7-4fa3-a709-17aeec3104c4
    

    Add it to /etc/fstab and set swap priorities:

    # swap was on /dev/sdb3 during installation
    UUID=e8532714-ad80-4aae-bee7-a9b37af63c8c none  swap sw,pri=1	0 0
    UUID=0f559a8c-67b7-4fa3-a709-17aeec3104c4 none	swap sw,pri=5	0 0
    

    Turn it on:

    sudo swapon -a
    

    Following those directions, dial back the kernel’s swappiness and limit the file cache growth:

    sudo sysctl -w vm.swappiness=1
    sudo sysctl -w vm.vfs_cache_pressure=50
    

    Those commands now live in /etc/sysctl.d/99-swappiness.conf:

    cat /etc/sysctl.d/99-swappiness.conf
    # Improve responsiveness by reducing cache swapping
    vm.swappiness=1
    vm.vfs_cache_pressure=50
    

    For whatever reason, WordPress turns underscores into blanks, so those obvious typos aren’t, really.

    And then it should Just Work.

    The box has 4 GB of RAM and, under normal circumstances, doesn’t swap at all, so I expect the USB drive should kick in only for extreme OpenSCAD models. The swappiness tuning should help during ordinary operation with large file operations.

    I have no results to report, but if something blows up, I know what changed…

  • Hall Effect LED Current Control: Crisp Gate Drive Shaping

    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
    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
    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
    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!)

  • Firefox Accounts: Total FAIL

    So I’m in the process of installing Xubuntu 14.04LTS on a box and get to the point where I’m ready to install various daemons and utilities, then tweak their settings, so it’s time to have the new Firefox inhale all my settings from the Firefox on my 13.10 desktop, which will let me find all my blog posts with that information. This used to be a simple matter of going into the new Firefox’s Preferences, getting a one-time pairing code, typing it into the other desktop, and away it went, synchronizing the two installations.

    But, no.

    While I wasn’t watching, Firefox crept up to Version 29 and, at some point, Mozilla introduced Firefox Accounts. Why would they do that? Here’s a hint:

    Firefox Accounts is a consumer account system which provides access to services run by Mozilla, such as Firefox Marketplace and the next version of Firefox Sync.

    Firefox Marketplace? Say no more: money changes everything!

    Oh, and the “next version of Firefox Sync” is totally incompatible with the “old version” used by all existing Firefox installations.

    But it gets worse (emphasis mine):

    What if I don’t want to update to the new Sync?

    • While the old version of Sync will continue to work, the latest version of Firefox doesn’t support adding new devices to the old version of Sync. This means that you won’t be able to sync with a new device.
    • Mozilla will continue to host the old version of Sync for a limited time to allow for migration to Firefox Accounts.

    In order to sync the 14.10 Firefox, I must upgrade the 13.10 Firefox, but after I do that, none of the other boxes will be able to sync with either of them. I haven’t checked whether Firefox Version 29 is offered for the 10.04LTS installation that’s running on the LinuxCNC boxes.

    My 13.10 desktop has endured many, many, many automatic Firefox upgrades during their recent version incrementing mania and, for whatever reason, it doesn’t offer “New Sync” as an option, despite being at the same Version 29 as the 14.04 installation. This is likely a problem with some Firefox extension or another, but I disabled them to no avail.

    When all else fails, you always create a new profile by starting the Firefox Profile Manager:

    firefox -profilemanager

    That works as expected; the new and completely bare profile let me create a new Firefox Account, which entails the usual to-ing and fro-ing with emailed one-time authorizations and suchlike. OK, now I can use the shiny new Firefox Marketplace, should I so desire. Be still, my heart!

    So, we progress.

    But my original intent was to get all the setup data into the 14.04 Firefox, so (on the 13.10 Firefox) I followed the directions about transferring the old settings into the new profile, which involves tediously hand-copying a bunch of files from one cryptic directory to another. This is a brutally user-hostile operation that only geeks should endure; there is absolutely no automation to be found.

    Having a new profile, albeit without any of the old extensions, I attempt to sync my settings, only to discover that the new Firefox Sync will not synchronize my stored passwords, which was pretty much the whole point of this exercise.

    Turns out that’s deliberate:

    Firefox Sync will not synchronize your passwords if a master password is set. If you would like to continue synchronizing your passwords, try removing your master password before synchronizing.

    Now, why would I have a master password? Because, long ago, the good folks at Mozilla highly recommended it (emphasis mine):

    It takes only fifteen seconds for a prying user sitting at your computer to see the list of all the passwords you have told Firefox or Thunderbird to save. The list is shown plain as day. It can include webmail and forum passwords or email server passwords. Using a Master Password is highly recommended, to prevent such prying users from seeing the list. By setting a Master Password, anyone using your profile will be prompted to enter the master password when access to your stored passwords is needed.

    So, the new Firefox Sync requires a Firefox Account that doesn’t do anything I need done and, in order to sync my 13.10 settings into the 14.04 box, I must have a new Firefox Account and make both Firefox installations less secure.

    I think it’s possible to remove the master password, sync the stored passwords, then restore the master password. When you remove the password, you get a confirmation message:

    You have deleted your Master Password. Your stored web and email passwords, form data, and private keys will not be protected.

    Firefox allegedly uses the Gnome keyring to get a master password protecting the whole Firefox session, but displaying all the stored passwords is just a few clicks away after that; needless to say, Firefox on 13.10 doesn’t use the keyring. Given that Chromium on Xubuntu 13.10 does not use the Gnome keyring, it’s entirely unprotected. Maybe the 14.04 box will use the keyring for both browsers?

    What the hell do those people smoke? I want some of that, right here, right now!

    Verily, money changes everything…

  • 3D Printing Demo at the HV Boy Scout CamporALL: Image Processing

    More than five hours of stand-up comedy condensed into two minutes of frenzied video showing activity like this:

    3D Printing Demo - HV Scout CamporALL 2014
    3D Printing Demo – HV Scout CamporALL 2014

    The Sony HDR-AS30V perched atop a tripod behind the table, where only one errant Scout bumped it, recording one image every 5 seconds. The non-adjustable focus seems biased for selfies, but the compression definitely produces softer images in subdued lighting conditions, so it’s hard to say.

    Each NP-BX1 battery lasts about 2.5 hr in that mode and I brought all three, but simply forgot to install the third one. As a result, we don’t get to see the last 2+ hours… it was a long day.

    The “image processing” behind the movie went a little something like this, modulo a few edits to elide my blundering around:

    sudo mount /dev/sdb1 /mnt/backup
    sudo mount -o uid=ed /dev/sdd1 /mnt/part
    mkdir /mnt/backup/Video/2014-05-17
    rsync -au /mnt/part/DCIM/100MSDCF/ /mnt/backup/Video/2014-05-17
    rsync -au /mnt/part/DCIM/101MSDCF/ /mnt/backup/Video/2014-05-17
    cd /tmp
    mkdir Video
    cd Video
    sn=1 ; for f in /mnt/backup/Video/2014-05-17/*JPG ; do printf -v dn 'dsc%05d.jpg' "$(( sn++ ))" ; cp -a $f $dn ; done
    mkdir Shrink
    for f in *jpg ; do convert $f -resize 50% Shrink/$f ; done
    cd Shrink/
    avconv -r 30 -i dsc%05d.jpg -q 5 3DPrinting-q5.mp4
    mv 3DPrinting-q5.mp4 "3D Printing Demo - HV Scout CamporALL 2104.mp4"
    

    One could, of course, do all that in fewer steps, if one knew the answers ahead of time, which should may apply when I refer back to this post.

    Using rsync -au to copy the files from the camera to the 2 TB backup drive neatly solves the problems that occur when the camera’s USB port abruptly disconnects itself during the copy: rsync can recover without losing or trashing any files. Alas, after the camera disconnects, it requires a power cycle to recover its wits.

    The USB camera connection reads data at 6 MB/s. Removing the MicroSD card and jamming it in the card-reader slot on my monitor runs at 18 MB/s. Apart from the fact that the MicroSD card seems so flimsy, I wonder how long the spring-detent latch inside the camera will continue working. On the other paw, when the USB port finally breaks, it’ll take the GPS assist data path along with it.

    Not shown: the rename 'y/A-Z/a-z/' *JPG that converts the original filenames to lowercase, which I did after the fact. Because blundering around, OK?

    The 3964 original 1920×1080 images, hot from the camera, weigh in at 2.2 GB and the half-size video emerged at 118 MB. The default avconv quality setting produces surprisingly crappy results, so I used -q 5. Some after-the-fact fiddling showed that -qscale 5 produces the same file size with about the same apparent quality.

    None of that matters, because Youtube set the maximum resolution to 480 and applied ruthless compression. Now I know better…

  • Extracting Frames From A Video File

    Using avconv (formerly ffmpeg):

    avconv -ss 00:07:05 -i MAH00016.MP4 -t 2 -f image2 -q 1 Image-%03d.jpeg
    

    The options:

    • -ss starting time in hh:mm:ss (or seconds)
    • -i input file
    • -t duration in seconds (or hh:mm:ss)
    • -f mux/demux for still images
    • -q quality (1 = best)

    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
    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.

  • 3D Printed Things I’ve Designed: Brag Sheets

    The whole reason I got a 3D printer in the first place was to make things that would otherwise be too difficult or tedious by hand or on a CNC mill. Most of the things I make look like brackets and I don’t do sculptures … this stuff solves problems!

    Being able to go from “I need a part shaped like that” to holding the thing in my hand a few hours (or, for complex designs, days) later is empowering. Being able to adjust a dimension by changing the source code and “recompiling” to get a new part is wonderful.

    These five slides from the presentation show my answers to the question “Why would anyone want a 3D printer?” Clicky for more dots.

    Things I Designed - 1
    Things I Designed – 1
    Things I Designed - 2
    Things I Designed – 2
    Things I Designed - 3
    Things I Designed – 3
    Things I Designed - 4
    Things I Designed – 4
    Things I Designed - 5
    Things I Designed – 5

    You can find those and more by searching for OpenSCAD source code.

    They go along with the sheets of solid models.

  • Slicing Anomaly: Resolved, With Cross-Check

    Some pix that serve as a stick in the ground showing that my current Slic3r configuration constellation doesn’t produce thin infill

    All of the layers in the 20 mm calibration cube look just like this:

    Solid cube - Slic3r normal infill
    Solid cube – Slic3r normal infill

    The bottom layer of the Tux mold comes out solid:

    Tux thread fill - bottom
    Tux thread fill – bottom

    As does the top:

    Tux thread fill - top
    Tux thread fill – top

    The Gcode Analyzer algorithm that assigns colors to numeric values tends to produce many aliases, although most of the time you can figure out what’s going on. If somebody wants to dive into the code, I’d like to have unique colors and get the color table sorted in ascending order.

    The current Slic3r configuration:

    # generated by Slic3r 1.1.1 on Sat May  3 10:31:36 2014
    avoid_crossing_perimeters = 0
    bed_size = 190,250
    bed_temperature = 70
    bottom_solid_layers = 3
    bridge_acceleration = 0
    bridge_fan_speed = 100
    bridge_flow_ratio = 1
    bridge_speed = 150
    brim_width = 0
    complete_objects = 0
    cooling = 1
    default_acceleration = 0
    disable_fan_first_layers = 1
    duplicate_distance = 6
    end_gcode = ;-- Slic3r End G-Code for M2 starts --\n;  Ed Nisley KE4NZU - 15 November 2013\nM104 S0		; drop extruder temperature\nM140 S0		; drop bed temperature\nM106 S0		; bed fan off\nG1 Z180 F2000	; lower bed\nG1 X130 Y125 F30000	; nozzle to right, bed front\nM84     	; disable motors\n;-- Slic3r End G-Code ends --
    external_perimeter_speed = 25
    external_perimeters_first = 0
    extra_perimeters = 1
    extruder_clearance_height = 25
    extruder_clearance_radius = 15
    extruder_offset = 0x0
    extrusion_axis = E
    extrusion_multiplier = 1.07
    extrusion_width = 0.4
    fan_always_on = 0
    fan_below_layer_time = 30
    filament_diameter = 1.79
    fill_angle = 45
    fill_density = 100%
    fill_pattern = rectilinear
    first_layer_acceleration = 0
    first_layer_bed_temperature = 70
    first_layer_extrusion_width = 0.4
    first_layer_height = 100%
    first_layer_speed = 25
    first_layer_temperature = 175
    g0 = 0
    gap_fill_speed = 50
    gcode_arcs = 0
    gcode_comments = 0
    gcode_flavor = reprap
    infill_acceleration = 0
    infill_every_layers = 3
    infill_extruder = 1
    infill_extrusion_width = 0
    infill_first = 1
    infill_only_where_needed = 0
    infill_speed = 150
    interface_shells = 0
    layer_gcode = 
    layer_height = 0.2
    max_fan_speed = 100
    min_fan_speed = 75
    min_print_speed = 4
    min_skirt_length = 15
    notes = 
    nozzle_diameter = 0.35
    only_retract_when_crossing_perimeters = 1
    ooze_prevention = 0
    output_filename_format = [input_filename_base].gcode
    overhangs = 1
    perimeter_acceleration = 0
    perimeter_extruder = 1
    perimeter_extrusion_width = 0.4
    perimeter_speed = 150
    perimeters = 2
    post_process = 
    print_center = 0,0
    raft_layers = 0
    randomize_start = 1
    resolution = 0.05
    retract_before_travel = 1
    retract_layer_change = 0
    retract_length = 1
    retract_length_toolchange = 5
    retract_lift = 0
    retract_restart_extra = 0
    retract_restart_extra_toolchange = 0
    retract_speed = 60
    skirt_distance = 3
    skirt_height = 1
    skirts = 3
    slowdown_below_layer_time = 20
    small_perimeter_speed = 25
    solid_fill_pattern = rectilinear
    solid_infill_below_area = 5
    solid_infill_every_layers = 0
    solid_infill_extrusion_width = 0
    solid_infill_speed = 150
    spiral_vase = 0
    standby_temperature_delta = -5
    start_gcode = ;-- Slic3r Start G-Code for M2 starts --\n;  Ed Nisley KE4NZU - 15 Nov 2013\n;  28 Feb 2014 - 6 Mar 2014 - tweak Z offset\n; Z-min switch at platform, must move nozzle to X=130 to clear\nM140 S[first_layer_bed_temperature]	; start bed heating\nG90				; absolute coordinates\nG21				; millimeters\nM83				; relative extrusion distance\nG92 Z0			; set Z to zero, wherever it might be now\nG1 Z10 F1000	; move platform downward to clear nozzle; may crash at bottom\nG28 Y0			; home Y to be sure of clearing probe point\nG92 Y-127 		; set origin so 0 = center of plate\nG28 X0			; home X\nG92 X-95		; set origin so 0 = center of plate\nG1 X130 Y0 F30000	; move off platform to right side, center Y\nG28 Z0			; home Z with switch near center of platform\nG92 Z-4.40		; set origin to measured z offset\nG0 Z2.0			; get air under switch\nG0 Y-127 F10000	; set up for priming, zig around corner\nG0 X0			;  center X\nM109 S[first_layer_temperature]	; set extruder temperature and wait\nM190 S[first_layer_bed_temperature]	; wait for bed to finish heating\nG1 Z0.0 F500	; put extruder near plate \nG1 E25 F300		; prime to get pressure, generate blob\nG1 Z5 F2000		; rise above blob\nG1 X15 Y-125 F20000	; jerk away from blob, move over surface\nG1 Z0.0 F1000	; dab nozzle to attach outer snot to platform\nG4 P1			; pause to attach\nG1 X35 F500		; slowly smear snot to clear nozzle\nG1 Z1.0 F2000	; clear bed for travel\n;-- Slic3r Start G-Code ends --
    start_perimeters_at_concave_points = 1
    start_perimeters_at_non_overhang = 1
    support_material = 0
    support_material_angle = 0
    support_material_enforce_layers = 0
    support_material_extruder = 1
    support_material_extrusion_width = 0
    support_material_interface_extruder = 1
    support_material_interface_layers = 0
    support_material_interface_spacing = 0
    support_material_pattern = honeycomb
    support_material_spacing = 2.5
    support_material_speed = 150
    support_material_threshold = 0
    temperature = 175
    thin_walls = 1
    threads = 2
    toolchange_gcode = 
    top_infill_extrusion_width = 0.4
    top_solid_infill_speed = 25
    top_solid_layers = 3
    travel_speed = 250
    use_firmware_retraction = 0
    use_relative_e_distances = 0
    vibration_limit = 0
    wipe = 0
    z_offset = 0