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

  • Devilspie2: Lua Scripts

    Prompted by that suggestion, here’s the current collection of Devilspie2 scripts (in ~/.config/devilspie2/) that make my dual-monitor setup (left: 2560×1440 landscape, right: 1050×1680 portrait) usable with a single X session in Xubuntu 12.10. The window manager understands about the monitor layout, so maximizing a window will fill whatever monitor it’s currently occupying.

    acroread.lua — maximized on portrait

    if (get_window_name()=="Adobe Reader") then
          unmaximize();
          set_window_geometry(0,0,1000,100);
          set_window_geometry(2561,0,1000,100);
          maximize();
    end
    

    chromium.lua — right half of landscape

    if (get_application_name()=="Chromium" and get_window_name() ~= "Print") then
       set_window_geometry(1400,0,1150,1200);
       maximize_vertically();
    end
    

    debugging.lua — handy with –debug option

    debug_print("Window Name: " .. get_window_name());
    debug_print("Application name: " .. get_application_name());
    

    digikam.lua — right half of landscape, force large Search dialog, dammit

    if (get_application_name() == "Digikam") then
    	debug_print("DigiKam conditional - top");
    	if (get_window_name() == "Advanced Search") then
    		debug_print("Digikam - Adv Search");
    		set_window_geometry(750,100,1000,1300);
    	else
    		debug_print("Main DigiKam window");
    		set_window_geometry(0,0,1400,1000);
    		maximize_vertically();
    	end
    end
    

    firefox.lua — left half of landscape, enlarge dialogs

    if (get_application_name()=="Firefox") then
    	debug_print("FF conditional - top");
    	if (get_window_name() == "Print") then
    		set_window_position(700,350);
    	elseif (0 == string.find(get_window_name(),"Password")) then
    		set_window_position(0,0);
    		maximize_vertically();
    	end
    end
    

    gimp.lua — force Gutenprint dialog to the top, dammit

    if (get_application_name() == "GNU Image Manipulation Program") then
    	debug_print("GIMP conditional - top");
    	if (string.find(get_window_name(),"Print")) then
    		debug_print("GIMP - GutenPrint")
    		set_window_position(700,350);
    		make_always_on_top();
    	else
    		debug_print("GIMP - Main window");
    	end
    end
    

    passwords.lua — put password dialogs in mid-screen

    if (get_window_name()=="Password Required") then
    	debug_print("Password");
    	set_window_position(700,350);
    end
    

    pronterface.lua — force to middle-ish of Desktop 2

    if (get_window_name()=="Printer Interface") then
       set_window_workspace(2);
       set_window_position(1200,750);
    end
    

    slic3r.lua — force to right side of Desktop 2

    if (get_window_name()=="Slic3r") then
       set_window_workspace(2);
       set_window_geometry(1600,0,700,700);
    end
    

    terminal.lua — maximized on portrait

    if (get_window_name()=="Terminal") then
       set_window_position(2561,0);
       maximize();
    end
    

    thunderbird.lua — left half of landscape, force big dialogs

    if (get_application_name() == "Thunderbird") then
    	debug_print("TBird conditional - top");
    	if (1 == string.find(get_window_name(),"Print")) then
    		debug_print("TBird - print...");
    		set_window_position(700,350);
    	elseif (string.find(get_window_name(),"Sending") or
    			string.find(get_window_name(),"Confirm") or
    			string.find(get_window_name(),"Processing")) then
    		debug_print("TBird - generic dialog");
    		set_window_position(200,600);
    	elseif (string.find(get_window_name(),"Write:")) then
    		debug_print("TBird - writing");
    		set_window_geometry(1300,0,900,600);
    		maximize_vertically();
    	elseif (0 == string.find(get_window_name(),"Password")) then
    		debug_print("Main TBird window?");
    		debug_print(" name: ",get_window_name());
    		set_window_geometry(0,0,1300,1200);
    		maximize_vertically();
    	end
    end
    
  • Makergear M2: Bridge Torture Test

    Although I’ve pretty much given up on torture tests, I saw a note about the troubles someone had with Triffid Hunter’s Bridge Torture Test object. I did a bit of tweaking to the OpenSCAD source to shorten the struts and add the pads (which could be done with Slic3r’s Brim settings), but it’s otherwise about the same. The clear span is about 50 mm:

    Bridge Torture Test - solid model
    Bridge Torture Test – solid model

    Using my usual settings, with no special setup, the front looked OK:

    Bridge torture test - overview
    Bridge torture test – overview

    One strand came out rather droopy:

    Bridge torture test - front
    Bridge torture test – front

    The bottom layer of the bridge isn’t as consolidated as it could be:

    Bridge torture test - bottom
    Bridge torture test – bottom

    The overall speed dropped considerably as the Cool setting limited the layer time to 20 seconds; the Bridge settings didn’t apply.

    I could probably tighten the bottom strands a bit, but it’s OK for a first pass.

    The OpenSCAD source code:

    bridge_length = 50;
    bridge_angle = 0;
    bridge_width = 5;
    
    strut_height = 4;
    
    layer_height = 0.25;
    extrusion_width = 0.5;
    
    bridge_recovery_layers = 2 / layer_height;
    
    module foot() {
    	rotate([0, 0, 180/4]) cylinder(r=bridge_width * 2, h=layer_height, $fn=4);
    }
    
    module strut() {
    	rotate([0, 0, 180/4])
    	difference() {
    		cylinder(r1=bridge_width / 1.5 / cos(45), r2=bridge_width / 2 / cos(45),
    			h=strut_height, $fn=4);
    		translate([0, 0, -1]) cylinder(r1=(bridge_width / 1.5 - extrusion_width * 2) / cos(45),
    			r2=(bridge_width / 2 - extrusion_width * 2) / cos(45), h=10, $fn=4);
    	}
    }
    
    translate([-bridge_length/2,0,0])
    rotate(bridge_angle) {
    	translate([00, 0, 0]) foot();
    	translate([bridge_length + bridge_width, 0, 0]) foot();
    
    	translate([00, 0, 0]) strut();
    	translate([bridge_length + bridge_width, 0, 0]) strut();
    
    	translate([bridge_width / -2, bridge_width / -2, strut_height]) cube([bridge_length + bridge_width * 2, bridge_width, layer_height * bridge_recovery_layers]);
    
    	translate([bridge_length / 2 + bridge_width / 2, 0, strut_height + layer_height * bridge_recovery_layers])
    	difference() {
    		cylinder(r=bridge_width / 2, h=1, $fn=32);
    		cylinder(r=bridge_width / 2 - extrusion_width, h=1.01, $fn=32);
    	}
    }
    
  • Why I Won’t Fix Your Windows PC

    Got a call from a friend who was having trouble getting BitDefender to accept its new license key, so I drove over; she’s at the top of a killer hill and I’d already biked my two dozen miles for the day. Solving that problem was straightforward, if you happen to know that they use “authorization” and “license” as synonyms and that you access the key entry dialog by clicking on a text field that doesn’t look at all clickable.

    I should have declared victory and returned to the Basement Laboratory, but, no, I had to be a nice guy.

    BitDefender kvetched that it had been 777 days since its last scan, so I set up some regularly scheduled scans and automagic updates for everything in sight; we agreed she’d just let the thing run overnight on Mondays to get all that done.

    BitDefender also suggested a handful of critical Windows XP updates, plus the usual Adobe Flash and Reader updates, plus some nonsense about Windows Live Messenger that seemed to require downloading and installing a metric shitload of Microsoft Bloatware. Rather than leave all that for next Monday’s unattended update, I unleashed the critical ones, did the Flash and Reader updates, and stuffed the Messenger update back under the rug.

    Then AOL recommended an urgent update to AOL Desktop 9.7. She has a couple of AOL email addresses, mostly for historic reasons, and I asked if she ever used the AOL Desktop. She wasn’t sure, so I lit up the installed AOL Desktop 9.6: “Oh, that’s how I get all my email!” OK, so we’ll update that, too.

    After all the thrashing was done, the system rebooted and presented us with the single most unhelpful error message I’ve ever seen:

    Windows Error - Ordinal Not Found
    Windows Error – Ordinal Not Found

    No, you chowderheads, that is not OK…

    Searching on the obvious terms indicated this had something to do with Internet Explorer 8 (remember IE 8?) and produced a number of irrelevant suggestions. The least awful seemed to involve running the Microsoft System File Checker utility:

    sfc /scannow

    Which I did.

    It ran for the better part of an hour, then suggested a reboot. During the shutdown, it replaced 29 files at an average of about 5 minutes per file.

    After which, Windows restarted and displayed exactly the same error message. Actually, a series of them; various programs couldn’t locate a fairly wide selection of ordinals in several DLLs.

    OK. I give up.

    We located a tech who does this sort of thing for a living. I’ve offered to split the cost of getting the box up and running again, with the understanding that it may be easier to start with a fresh off-lease Dell box running Windows 7 than to exhume an aging Windows XP installation.

    I stopped caring about Windows toward the end of the last millennium and now keep a Token Windows Box only for hardware like the HOBOWare dataloggers and software like TurboTax.

    Other than that, well…

    I. Don’t. Care.

  • Creating a Curvelicious Cookie Cutter

    So, for reasons I need not go into, I needed an OpenSCAD solid model of a custom cookie cutter produced on an Afinia 3D printer from a Trimble Sketchup model:

    Afinia Robot Cutter - on raft
    Afinia Robot Cutter – on raft

    The cutter is still attached to the raft that, it seems, is required for passable results on the Afinia’s platform.

    Having already figured out how to wrap a cutter around a shape, the most straightforward procedure starts by extracting the cutter’s shape. So, lay the cutter face down on the scanner and pull an image into GIMP:

    Afinia Robot - scan
    Afinia Robot – scan

    Blow out the contrast to eliminate the background clutter, then posterize to eliminate shadings:

    Afinia Robot - scan enhanced
    Afinia Robot – scan enhanced

    Select the black interior region, grow the selection by a pixel or two, then shrink it back to eliminate (most of) the edge granularity, plunk it into a new image, and fill with black:

    Afinia Robot - scan filled
    Afinia Robot – scan filled

    Now the magic happens…

    Import the bitmap image into Inkscape. In principle, you can auto-trace the bitmap outline and clean it up manually, but a few iterations of that convinced me that it wasn’t worth the effort. Instead, I used Inkscape’s Bézier Curve tool to drop nodes (a.k.a. control points) at all the inflection points around the image, then warped the curves to match the outline:

    Afinia Robot - Bezier spline fitting
    Afinia Robot – Bezier spline fitting

    If you’re doing that by hand, you could start with the original scanned image, but the auto-trace function works best with a high-contrast image and, after you give up on auto-tracing, you’ll find it’s easier to hand-trace a high-contrast image.

    Anyhow, the end result of all that is a smooth path around the outline of the shape, without all the gritty details of the pixelated version. Save it as an Inkscape SVG file for later reference.

    OpenSCAD can import a painfully limited subset of DXF files that, it seems, the most recent versions of Inkscape cannot produce (that formerly helpful tutorial being long out of date). Instead, I exported (using “Save as”) the path from Inkscape to an Encapsulated Postscript file (this is a PNG, as WordPress doesn’t show EPS files):

    Afinia Robot - Bezier Curves.eps
    Afinia Robot – Bezier Curves.eps

    It’s not clear what the EPS file contains; I think it’s just a list of points around the path that doesn’t include the smooth Bézier goodness. That may account for the grittiness of the next step, wherein the pstoedit utility converts the EPS file into a usable DXF file:

    pstoedit dxf:-polyaslines Afinia\ Robot\ -\ Bezier\ Curves.eps Afinia\ Robot\ -\ outline.dxf
    

    Unfortunately, either the EPS file doesn’t have enough points on each curve or pstoedit automatically sets the number of points and doesn’t provide an override: contrary to what you (well, I) might think, the -splineprecision option doesn’t apply to whatever is in the EPS file. In any event, the resulting DXF file has rather low-res curves, but they were good enough for my purposes and OpenSCAD inhaled the DXF and emitted a suitable STL file:

    Afinia Robot - shape slab
    Afinia Robot – shape slab

    To do that, you set the Layout variable to “Slab”, compile the model, and export the STL.

    Being interested only in the process and its results, not actually cutting and baking cookies, I tweaked the OpenSCAD parameters to produce stumpy “cutters”:

    Afinia Robot - solid model
    Afinia Robot – solid model

    You do that by setting the Layout variable to “Build”, compile the model, and export yet another STL. In the past, this seemed to be a less fragile route than directly importing and converting the DXF at each stage, but that may not be relevant these days. In any event, having an STL model of the cookie may be useful in other contexts, so it’s not entirely wasted effort.

    Run the STL through Slic3r to get the G-Code as usual.

    The resulting model printed in about 20 minutes apiece on the M2:

    Robot Cutter - stumpy version
    Robot Cutter – stumpy version

    As it turns out, the fact that the M2 can produce ready-to-use cutters, minus the raft, is a strong selling point.

    Given a workable model, the next step was to figure out the smallest possible two-thread-wide cutter blade, then run variations of the Extrusion Factor to see how that affected surface finish. More on that in a while.

    The OpenSCAD source isn’t much changed from the original Tux Cutter; the DXF import required different scale factors:

    // Robot cookie cutter using Minkowski sum
    // Ed Nisley KE4ZNU - Sept 2011
    // August 2013 adapted from the Tux Cutter
    
    Layout = "Build";				// Build Slab
    
    //- Extrusion parameters - must match reality!
    
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    MaxSize = 150;				// larger than any possible dimension ...
    
    Protrusion = 0.1;
    
    //- Cookie cutter parameters
    
    Size = 95;
    
    TipHeight = IntegerMultiple(3.0,ThreadThick);
    TipThick = 1.5*ThreadWidth;			// 1.5* = thinnest 2-thread wall, 1.0* thread has gaps
    
    WallHeight = IntegerMultiple(1.0,ThreadThick);
    WallThick = 4.5*ThreadWidth;
    
    LipHeight = IntegerMultiple(1.0,ThreadWidth);
    LipThick = IntegerMultiple(5,ThreadWidth);
    
    //- Wrapper for the shape of your choice
    
    module Shape(Size) {
      Robot(Size);
    }
    
    //- A solid slab of Tux goodness in simple STL format
    // Choose magic values to:
    //		center it in XY
    //		reversed across Y axis (prints with handle on bottom)
    //		bottom on Z=0
    //		make it MaxSize from head to feet
    
    module Tux(Scale) {
      STLscale = 250;
      scale(Scale/STLscale)
    	translate([105,-145,0])
    	  scale([-1,1,24])
    		import(
    		  file = "/mnt/bulkdata/Project Files/Thing-O-Matic/Tux Cookie Cutter/Tux Plate.stl",
    		  convexity=5);
    }
    
    module Robot(Scale) {
        STLscale = 100.0;
        scale(Scale / STLscale)
    			scale([-1,1,10])
    				import("/mnt/bulkdata/Project Files/Thing-O-Matic/Pinkie/M2 Challenge/Afinia Robot.stl",
    					convexity=10);
    }
    
    //- Given a Shape(), return enlarged slab of given thickness
    
    module EnlargeSlab(Scale, WallThick, SlabThick) {
    
    	intersection() {
    	  translate([0,0,SlabThick/2])
    		cube([MaxSize,MaxSize,SlabThick],center=true);
    	  minkowski(convexity=5) {
    		Shape(Scale);
    		cylinder(r=WallThick,h=MaxSize,$fn=16);
    	  }
    	}
    
    }
    
    //- Put peg grid on build surface
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      RangeX = floor(100 / Space);
      RangeY = floor(125 / Space);
    
    	for (x=[-RangeX:RangeX])
    	  for (y=[-RangeY:RangeY])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //- Build it
    
    ShowPegGrid();
    
    if (Layout == "Slab")
    	Shape(Size);
    
    if (Layout == "Build")
    	difference() {
    	union() {
    		translate([0,0,(WallHeight + LipHeight - Protrusion)])
    		EnlargeSlab(Size,TipThick,TipHeight + Protrusion);
    		translate([0,0,(LipHeight - Protrusion)])
    		EnlargeSlab(Size,WallThick,(WallHeight + Protrusion));
    		EnlargeSlab(Size,LipThick,LipHeight);
    	}
    	Shape(Size);					// punch out cookie hole
    	}
    
  • Hall Effect LED Current Control: Measurements and Firmware

    With that hardware in hand, a dab of firmware produces this result:

    Hall Current Sense - 120 mA 25 ms 250 ms
    Hall Current Sense – 120 mA 25 ms 250 ms

    A detailed look at one pulse:

    Hall Current Sense - 120 mA 25 ms 250 ms - detail
    Hall Current Sense – 120 mA 25 ms 250 ms – detail

    The top trace is the total LED current, nominally 120 mA, at 50 mA/div. The ripple (which is a nice triangle waveform at a faster sweep) comes from the 32 kHz PWM pulse train, despite passing through a 1 ms RC filter; the MOSFET runs in the linear region and makes a great amplifier.

    The middle trace is the MOSFET drain voltage at 1 V/div. The on-state voltage runs around 1.6 V, so the LEDs see about 5.9 V at 120 mA, about what you’d expect, with the little bit of PWM ripple accounting for the current sawtooth in the top trace. The off-state voltage is only 3.8 V, because the LEDs soak up the rest; it’s about 1.2 V per LED.

    The bottom trace is the current-sense amp output. The 1 nF cap in the op amp feedback loop rolls it off at 600 Hz, so there’s not much ripple at all in there. That goes directly to the Arduino’s ADC, where it’s further averaged over 10 samples.The LEDs take a couple of milliseconds to get up to full intensity, but it’s much faster than an incandescent filament: this thing blinks rather than flashes.

    The current in each LED string runs from about 15 mA to 25 mA, with all the “old” LEDs at the low end and the “new” LED strings at the high end. Using unsorted LEDs from the same batch will probably be OK, although I’ll measure them just to see what they’re like.

    The LEDs dissipate 700 mW and the MOSFET wastes 192 mW, so the efficiency is around 79%. Not too shabby for a linear regulator and it only gets better as the battery discharges. The toroid winding burns maybe 300 μW, so it’s not in the running; to be fair, a 1 Ω sense resistor would account for only 14 mW, but it would drop 120 mV instead of 3 mV, which is what matters more when the battery voltage drops.

    That’s during the pulse, which should have a duty cycle under 25% or so, which means 175 mW and 48 mW on the average. Obviously, no heatsinks needed: each LED runs at 7 mW average under those conditions.

    The firmware steps the gate voltage by the smallest possible increment, about 20 mV = 5 V / 256. The feedback loop adjusts the gate voltage in single steps to avoid goosing the LEDs with too much current; a binary search wouldn’t work very well at all. I think it’d be a good idea to build a table of transconductance (gate voltage to LED current) by ramping the gate voltage during startup, then fine-tune the coefficients during each pulse.

    The console log tells the tale:

    Hall effect Current Regulator
    Ed Nisley - KE4ZNU - August 2013
    Given Vcc: 5010 mV
    Given VBatt divider ratio: 0.500
    Bandgap reference voltage: 1105 mV
    Battery voltage: 7551 mV
    Nulling Hall sensor offset: 0 PWM
    Final Hall sensor offset: 209 PWM
    Gate voltage: 1947 mV  LED Current: 5 mA
    Gate voltage: 1966 mV  LED Current: 6 mA
    Gate voltage: 1986 mV  LED Current: 6 mA
    Gate voltage: 2005 mV  LED Current: 7 mA
    Gate voltage: 2025 mV  LED Current: 8 mA
    Gate voltage: 2040 mV  LED Current: 8 mA
    Gate voltage: 2064 mV  LED Current: 10 mA
    Gate voltage: 2084 mV  LED Current: 11 mA
    Gate voltage: 2103 mV  LED Current: 13 mA
    Gate voltage: 2123 mV  LED Current: 14 mA
    Gate voltage: 2142 mV  LED Current: 17 mA
    Gate voltage: 2162 mV  LED Current: 19 mA
    Gate voltage: 2177 mV  LED Current: 22 mA
    Gate voltage: 2201 mV  LED Current: 25 mA
    Gate voltage: 2221 mV  LED Current: 29 mA
    Gate voltage: 2240 mV  LED Current: 33 mA
    Gate voltage: 2255 mV  LED Current: 38 mA
    Gate voltage: 2275 mV  LED Current: 44 mA
    Gate voltage: 2294 mV  LED Current: 49 mA
    Gate voltage: 2314 mV  LED Current: 56 mA
    Gate voltage: 2333 mV  LED Current: 63 mA
    Gate voltage: 2353 mV  LED Current: 70 mA
    Gate voltage: 2372 mV  LED Current: 79 mA
    Gate voltage: 2392 mV  LED Current: 89 mA
    Gate voltage: 2412 mV  LED Current: 99 mA
    Gate voltage: 2431 mV  LED Current: 110 mA
    Gate voltage: 2451 mV  LED Current: 122 mA
    Gate voltage: 2431 mV  LED Current: 110 mA
    Gate voltage: 2451 mV  LED Current: 121 mA
    Gate voltage: 2431 mV  LED Current: 110 mA
    Gate voltage: 2451 mV  LED Current: 122 mA
    Gate voltage: 2431 mV  LED Current: 110 mA
    Gate voltage: 2451 mV  LED Current: 121 mA
    

    The current feedback tweaks the gate voltage by one PWM increment on each loop, so the LED current pulses alternate between 110 and 122 mA when the loop finally reaches the setpoint. This doesn’t make any practical difference, as each LED string’s current varies by a few mA, at most, but maybe there should be a deadband of a bit more than ±1/2 PWM increment around the actual current.

    The Arduino source code:

    // LED Curve Tracer
    // Ed Nisley - KE4ANU - August 2013
    
    #include <stdio.h>
    
    //----------
    // Pin assignments
    
    const byte PIN_READ_VBATT = 0;		// AI - battery voltage from divider
    const byte PIN_READ_CURRENT = 1;	// AI - current sense amp
    const byte PIN_READ_VGATE = 2;		// AI - actual gate voltage
    const byte PIN_READ_HALL = 3;		// AI - raw Hall sensor voltage
    
    const byte PIN_SET_BIAS = 11;		// PWM - VCC/2 bias voltage
    const byte PIN_SET_VGATE = 3;		// PWM - MOSFET gate voltage
    
    const byte PIN_HEARTBEAT = 13;		// DO - Arduino LED
    const byte PIN_SYNC = 2;			// DO - scope sync output
    
    //----------
    // Constants
    
    const float MaxLEDCurrent = 0.120;		// maximum LED current
    
    const float Vcc = 5.01; 				// Arduino supply -- must be measured!
    
    const float VBattRatio = 3.03/6.05;		// measured division ratio for battery divider
    
    const float VStep = Vcc/256;			// minimum PWM voltage increment = 5 V / 256
    
    const float IGain = 0.100;				// Hall sense voltage to LED current
    
    const byte PWM_Settle = 10;				// PWM settling time ms
    
    // Timer prescaler = 1:1 for 32 kHz PWM
    #define TCCRxB 0x01
    
    #define MK_UL(fl,sc) ((unsigned long)((fl)*(sc)))
    #define MK_U(fl,sc) ((unsigned int)((fl)*(sc)))
    #define MK_I(fl,sc) ((int)((fl)*(sc)))
    
    //----------
    // Globals
    
    float AVRef1V1;					// 1.1 V bandgap reference - calculated from Vcc
    
    float VBatt;					// battery voltage - calculated from divider
    float VGateSense;				// actual gate voltage - measured after PWM filter
    float ILEDSense;				// LED current from Hall effect sensor
    
    float VGateDrive;				// gate drive voltage
    
    byte PWMHallOffset;				// zero-field Hall effect sensor bias
    
    long unsigned long MillisNow, MillisThen;	// sampled millis() value
    
    //-- Read AI channel
    //      averages several readings to improve noise performance
    //		returns value in volts assuming known VCC ref voltage
    
    #define NUM_T_SAMPLES    10
    
    float ReadAI(byte PinNum) {
    
      word RawAverage;
    
      digitalWrite(PIN_SYNC,HIGH);					// scope sync
    
      RawAverage = (word)analogRead(PinNum);		// prime the averaging pump
    
      for (int i=2; i <= NUM_T_SAMPLES; i++) {
        RawAverage += (word)analogRead(PinNum);
      }
    
      digitalWrite(PIN_SYNC,LOW);
    
      RawAverage /= NUM_T_SAMPLES;
    
      return Vcc * (float)RawAverage / 1024.0;
    
    }
    
    //-- Set PWM output
    
    void SetPWMVoltage(byte PinNum,float PWMVolt) {
    
    byte PWM;
    
      PWM = constrain((byte)(255.0 * PWMVolt / Vcc),0,255);
    
      analogWrite(PinNum,PWM);
      delay(PWM_Settle);
    
    }
    
    //-- compute actual 1.1 V bandgap reference based on known VCC = AVcc (more or less)
    //		adapted from http://code.google.com/p/tinkerit/wiki/SecretVoltmeter
    
    float ReadBandGap(void) {
    
      word ADCBits;
      float VBandGap;
    
      ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);	// select 1.1 V input
      delay(2); // Wait for Vref to settle
    
      ADCSRA |= _BV(ADSC);										// Convert
      while (bit_is_set(ADCSRA,ADSC));
    
      ADCBits = ADCL;
      ADCBits |= ADCH<<8;
    
      VBandGap = Vcc * (float)ADCBits / 1024.0;
      return VBandGap;
    }
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //------------------
    // Set things up
    
    void setup() {
    
    float AVRef1V1;
    
      pinMode(PIN_HEARTBEAT,OUTPUT);
      digitalWrite(PIN_HEARTBEAT,LOW);	// show we arrived
    
      pinMode(PIN_SYNC,OUTPUT);
      digitalWrite(PIN_SYNC,LOW);		// show we arrived
    
      TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
      TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
      analogWrite(PIN_SET_VGATE,0);		// force gate voltage = 0
    
      Serial.begin(57600);
      fdevopen(&s_putc,0);				// set up serial output for printf()
    
      printf("Hall effect Current Regulator\r\nEd Nisley - KE4ZNU - August 2013\r\n");
    
      printf("Given Vcc: %d mV\r\n",MK_I(Vcc,1000.0));
      printf("Given VBatt divider ratio: 0.%d\r\n",MK_I(VBattRatio,1000.0));
    
      AVRef1V1 = ReadBandGap();			// compute actual bandgap reference voltage
      printf("Bandgap reference voltage: %d mV\r\n",MK_I(AVRef1V1,1000.0));
    
      VBatt = ReadAI(PIN_READ_VBATT) / VBattRatio;
      printf("Battery voltage: %d mV\r\n",MK_I(VBatt,1000.0));
    
      SetPWMVoltage(PIN_SET_VGATE,0.0);		// zero LED current
      PWMHallOffset = 0;
      analogWrite(PIN_SET_BIAS,PWMHallOffset);
      printf("Nulling Hall sensor offset: %d PWM\r\n",PWMHallOffset);
    
      do {
    	ILEDSense = IGain * ReadAI(PIN_READ_CURRENT);
    //	printf("Current Sense: %d mA - ",MK_I(ILEDSense,1000.0));
    	if (ILEDSense > 0.005) {
    		PWMHallOffset += 1;
    		analogWrite(PIN_SET_BIAS,PWMHallOffset);
            delay(PWM_Settle);
    //		printf("Step offset: %d PWM\r\n",PWMHallOffset);
    	}
      } while (ILEDSense > 0.005);
      printf("Final Hall sensor offset: %d PWM\r\n",PWMHallOffset);
    
      VGateDrive = 2.0;					// reasonable starting point
    
      MillisThen = millis();
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	if ((millis() - MillisThen) > 250) {
    		MillisThen = millis();
    
    		if (ILEDSense < MaxLEDCurrent) {
    			VGateDrive += VStep;
    		}
    		else if (ILEDSense > MaxLEDCurrent) {
    			VGateDrive -= VStep;
    		}
    
    		SetPWMVoltage(PIN_SET_VGATE,VGateDrive);
    
    		VGateSense = ReadAI(PIN_READ_VGATE);
    		printf("Gate voltage: %d mV  ",MK_I(VGateSense,1000.0));
    
    		ILEDSense = IGain * ReadAI(PIN_READ_CURRENT);
    		printf("LED Current: %d mA\r\n",MK_I(ILEDSense,1000.0));
    
    		delay(50 - PWM_Settle - 3);
    		SetPWMVoltage(PIN_SET_VGATE,0.0);
    
    		digitalWrite(PIN_HEARTBEAT,!digitalRead(PIN_HEARTBEAT));
    		digitalWrite(PIN_HEARTBEAT,!digitalRead(PIN_HEARTBEAT));
    	}
    }
    
  • Why Friends Don’t Let Friends Run Windows: Product Pictures? Really?

    This email worked its way through the filters:

    Dear Business Partner,

    We are very much interested in some of your product. We try to contact you online but you are not online so we decided to attach the picture of the product we need to dropbox and put it in your offline. Open the bellow link and download the attachment to preview the product we need:

    ... dropbox url snippage ... /Product%20Pics.rar

    Let me know if the product is still available for sale and how much it costs, also tell us the product details.

    Regards,
    Allen Moore,
    Procurement Officer,
    International Product Buyers

    Well, I don’t generally rebuff the humble, but I don’t have any “product” for sale. Also pulling the suspicion trigger:

    • To: Recipients <Procurement@Officer.com>
    • Subject: Open Attachment For Product Picture

    It’s not clear what “attach the picture of the product we need to dropbox and put it in your offline” might mean. Despite the Dropbox URL, the email sported an attachment named Product\ Pics.rar, showing they come from a different universe wherein every operating system has a native RAR extraction program.

    Being a dutiful citizen of the Interwebs, I did what the nice man asked:

    unrar e Product\ Pics.rar

    That produced a single file which RAR described thusly:

    Extracting Product Picjpg.SCR

    At least that’s what it looked like on the command line. I think they were trying to overwrite the SCR with the jpg, as the file name was really Product Pic<U+202E>RCS.gpj, but the Unicode U+20E bidirectional text control character seems to be in the wrong place. I think they wanted Product Pic.SCR<U+202E>gpj, but I also confess to having no experience with sixth-level Unicode direction reversal rendering.

    Anyhow, handing the entire RAR archive to VirusTotal produces the expected result:

    VirusTotal - Product Pics malware file
    VirusTotal – Product Pics malware file

    It’s disconcerting to see ClamAV asleep at the switch on this one, but signature detection has become decreasingly relevant these days.

    I opted to not respond to the request..

  • MAKE: Mistake

    MAKE - address blooper
    MAKE – address blooper

    I expect the blooper isn’t reciprocal; Dr Darwish probably didn’t get my “Dear Ed” salutation.

    Sorry, that title was just too good to pass up…