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.

Tag: Improvements

Making the world a better place, one piece at a time

  • Kenmore 158: Linearized Speed Control

    Plugging the normalized pedal position into the code that turns position into speed:

    case PD_RUN :
    	if (PedalPosition > 5) {
    		if (MotorDrive.State != RUNNING) {
    			EnableMotor();
    			MotorDrive.State = RUNNING;
    		}
    
    		BaseDAC.setVoltage(0x0fff,false);								// give it a solid pulse
    		SampleCurrent(PIN_CURRENT_SENSE);								// sample over a half cycle
    		if (DriveOnTime > CurrentSamplingTime) {
    			delay(DriveOnTime - CurrentSamplingTime);
    		}
    
    // Pedal to the metal?
    //   ... if so, stall here with motor drive fully on until the pedal releases
    
    		while ((MotorDrive.SpeedRange == SPEED_HIGH) && (PedalPosition >= 100)) {
    			PedalPosition = PedalPercent(ReadAI(PIN_PEDAL));
    		}
    
    		BaseDAC.setVoltage(0,false);									//  ... then turn it off
    
    		delay(map(constrain(PedalPosition,0,PedalMaxClamp),
    				0,100,
    				DriveOffTime,0));
    	}
    	else {
    		if (MotorDrive.State == RUNNING) {
    			if (MotorSensor.RPM) {
    				printf("Coast: %d\r\n",MotorSensor.RPM);
    				delay(100);
    			}
    			else {
    				printf("Parking ");
    				ParkNeedle(MotorDrive.ParkPosition);
    				MotorDrive.State = STOPPED;
    				printf(" stopped\r\n");
    			}
    		}
    	}
    	break;
    

    The magic happens in highlighted statement, which flips the sense of the pedal motion and linearly scales the result into a delay() value ranging from 120 ms (pedal barely pressed) down to 0 ms (pedal almost fully pressed). If the pedal is all the way down, then the preceding while() locks up until it’s released a bit, whereafter the delay will be nearly zero.

    That sorta-kinda worked, but the user community said that the pedal response required pushing too hard for top speed: it should get faster, sooner. The problem came from the simplistic way I set the speed: it was inversely proportional to the position.

    Plotting speed against pedal position shows the problem:

    Speed vs pedal - period control
    Speed vs pedal – period control

    I figured the right approach was to make the speed vary linearly with the pedal position, so the trick was to plot the off-time delay vs. the actual speed:

    Off-time delay vs speed - period control
    Off-time delay vs speed – period control

    The second-order equation bottles up a bunch of nonlinear things. Given that this was using the original code, I made the dubious assumption that more-or-less the same delay in the new code would produce more-or-less the same speed.

    The new code discards the current-sampling routine that I was using to get a fixed delay (because I don’t need to know the current in pulse-drive mode), then used that time for the floating-point calculation required to find the off-time delay. That chunk of code took a bit of fiddling to get right:

    case PD_RUN :
    	if (PedalPosition > 5) {
    		if (MotorDrive.State != RUNNING) {
    			EnableMotor();
    			MotorDrive.State = RUNNING;
    		}
    
    		BaseDAC.setVoltage(0x0fff,false);								// give it a solid pulse
    		MillisOn = millis() + (unsigned long)DriveOnTime;
    
    		TargetSpeed = (float)map(constrain(PedalPosition,0,PedalMaxClamp),
    				0,100,
    				0,700);						// quick and dirty 0 to 700 stitch/min range
    		OffTime = (int)((1.94e-4*TargetSpeed - 0.286)*TargetSpeed + 106.0);
    		OffTime = constrain(OffTime,0,120);								// clamp to reasonable range
    		MillisOff = MillisOn + (unsigned long)OffTime;					// compute end of off time
    
    		while (millis() <= MillisOn) {									// wait for end of pulse
    			continue;
    		}
    
    		if ((PedalPosition >= 100) && (MotorDrive.SpeedRange == SPEED_HIGH)) {	// pedal down in full speed mode?
    			printf("Full speed ... ");
    			OffTime = 0;
    			while (PedalPosition >= 100) {								//  ... full throttle!
    				PedalPosition = PedalPercent(ReadAI(PIN_PEDAL));
    			}
    			BaseDAC.setVoltage(0,false);								//  pedal released, start coasting
    			printf(" done\r\n");
    		}
    		else {															// pedal partially pressed
    			BaseDAC.setVoltage(0,false);								// pulse done, turn motor off
    			while (millis() <= MillisOff) {								// wait for end of off period
    				continue;
    			}
    		}
    	}
    

    But the result looks as pretty as you could possibly expect:

    Speed vs pedal - linearized speed control
    Speed vs pedal – linearized speed control

    The pedal still provides a soft-start transition from not moving to minimum speed, which remains an absolute requirement: having an abrupt transition to that straight line would be a Bad Thing. Fortunately, the nature of the magnet moving toward the Hall effect sensor gives you that for free.

    Although we’re still evaluating the results, the user community seems happier…

  • Kenmore 158: Normalized Pedal Position

    Adjusting the output voltage vs. position for the sewing machine’s food pedal quickly revealed that the code shouldn’t depend on the actual ADC values. That’s blindingly obvious in hindsight, of course.

    The maximum with the pedal in its overtravel region doesn’t change by much, because the Hall effect sensor output voltage saturates in a high magnetic field. I used a hardcoded word PedalMax = 870; which comes from 4.25 V at the ADC input.

    On the low end, the sensor output can change by a few counts depending on small position changes, so I sampled the (presumably released) pedal output during the power-on reset:

    	PedalMin = ReadAI(PIN_PEDAL);				// set minimum pedal input value
    	printf("Set PedalMin: %u\r\n",PedalMin);
    	PedalMaxClamp = 100;						// set upper speed limit
    
    

    Given the complete ADC range, this function normalizes a value to the range [0,100], conveniently converting the pedal position into a percent of full scale:

    int PedalPercent(word RawPos) {
    int Clamped;
    
    	Clamped = constrain(RawPos,PedalMin,PedalMax);
    	return map(Clamped,PedalMin,PedalMax,0,100);
    }
    

    Graphing the normalized values against pedal position would have the same shape as the ADC values. All I’m doing is rescaling the Y axis to match the actual input limits.

    The top of the main loop captures the pedal position:

    PedalADC = ReadAI(PIN_PEDAL);
    PedalPosition = PedalPercent(PedalADC);
    

    Now, it’s easy to add a slight deadband that ensures the sewing machine doesn’t start when you give the pedal a harsh look; the deadband is now a percent of full travel, rather than a hard-coded ADC count or voltage.

    For example, in needle-follows-pedal mode, you must press the pedal by more than 10% to start the stitch, slightly release it to finish the stitch, and then almost completely release it to proceed to the next stitch:

    	case PD_FOLLOW:
    		if (PedalPosition > 10) {
    			printf("Pedal Follow\r\n");
    			ParkNeedle(NS_DOWN);
    			do {
    				PedalPosition = PedalPercent(ReadAI(PIN_PEDAL));
    			} while (PedalPosition > 10);
    			ParkNeedle(NS_UP);
    			do {
    				PedalPosition = PedalPercent(ReadAI(PIN_PEDAL));
    			} while (PedalPosition > 2);
    		}
    		break;
    

    Adjusting percentages turns out to be much easier than fiddling with ADC values.

    Obvious, huh?

  • Kenmore 158: Pedal Recalibration

    With the Crash Test Dummy in place, Mary reports that the pedal required too much motion to reach the full speed position. The graph from the last time around shows the output as a function of pedal position:

    Hall sensor output vs pedal depression
    Hall sensor output vs pedal depression

    I’d done some fiddling around after recording that data, but it remained pretty close to the truth.

    A new scale, not quite in the same spot as the previous one:

    Kenmore 158 - Foot Pedal - motion recalibration
    Kenmore 158 – Foot Pedal – motion recalibration

    The two long lines mark the active region after I finished the mechanical tweaking described below; the pedal now produces nearly full output just beyond the 12 mm mark and has about that much overtravel. Measuring those values requires squeezing the pedal by hand, holding its position, and recording the ADC output dumped by the motor control program in the Arduino, a process that could be improved, but to not much benefit.

    The original pedal writeup goes into the gory details, but the bottom line is that the mechanical motion producing that output range depends on the length of the rod from the actuator bar to the magnet.

    The original version had a thin nut securing a screw inside the brass shaft to the actuator:

    Kenmore 158 - Hall speed control - prototype interior
    Kenmore 158 – Hall speed control – prototype interior

    I later swapped the nut for three washers, after observing that the nut wasn’t actually necessary, but that produced too much dead travel at the beginning of motion.

    We discovered that the actuator bar could slip off the end of the ramp cast into the pedal cover, jamming the end of the ramp, making the case extremely difficult to disassemble, and causing heartache & confusion. Affixing a pair of rubber feet to the rear wall of the pedal case with tapeless sticky keeps the bar about half a millimeter further forward and eliminates that problem:

    Kenmore 158 - foot pedal - short actuation
    Kenmore 158 – foot pedal – short actuation

    A second nut moved the brass rod forward, but that turned out to be a bit too much, so it now has a single, slightly thicker, nut. The 3D printed frame allows for far more travel in each direction than is strictly necessary, specifically to allow this fine tuning; it’s possible to make the rod’s resting position too close to the Hall effect sensor and have them collide at full travel, but I managed to avoid that.

    It’s impossible to measure anything with the case disassembled: each change requires reassembling everything, measuring, and iterating.

    After some of this & that, this graph shows the final output curve, with the Y axis in raw ADC counts at 100/div:

    Foot Pedal - ADC vs position - graph detail
    Foot Pedal – ADC vs position – graph detail

    The first 3 mm doesn’t produce much change in the output, it smoothly changes to the more-or-less linear ramp up to 12 mm, then tapers off to full output beyond 14 mm. That’s pretty close to the original graph, with full throttle occurring a bit more than 2 mm earlier; the difference between the two scales may have some effect. In any event, Mary reports it feels much better.

    Trust me: that matters.

    The original data from the first and second mods, plus a teeny version of that graph:

    Foot Pedal - ADC vs position
    Foot Pedal – ADC vs position
  • Adafruit Touch-screen TFT LCD Rotation

    The alert reader will have noted that the Kenmore 158 UI twisted around to a new orientation atop its fancy holder, with the USB port now poking out from the right side:

    Kenmore 158 UI - PCB holder
    Kenmore 158 UI – PCB holder

    That lets me position the whole affair to the right of the sewing machine, in what seems to be its natural position, without having the cable form a loop that would push it off the platform. It’s not entirely clear how we’ll keep a straight cable from pulling it off, but that’s in the nature of fine tuning.

    Anyhow, rotating the LCD isn’t a big deal, because the Adafruit library does all the heavy lifting:

    // LCD orientation: always landscape, 1=USB upper left / 3=USB lower right
    #define LCDROTATION 3
    
    ... snippage ...
    tft.begin();
    tft.setRotation(LCDROTATION);	// landscape, 1=USB upper left / 3=USB lower right
    

    Flipping the touch screen coordinates required just interchanging the “to” bounds of the map() functions, with a conditional serving as institutional memory in the not-so-unlikely event I must undo this:

    #if LCDROTATION == 1
    	p->x = map(t.y, TS_Min.y, TS_Max.y, 0, tft.width());	// rotate & scale to TFT boundaries
    	p->y = map(t.x, TS_Min.x, TS_Max.x, tft.height(), 0);	//   ... USB port at upper left
    #elif LCDROTATION == 3
    	p->x = map(t.y, TS_Min.y, TS_Max.y, tft.width(), 0);	// rotate & scale to TFT boundaries
    	p->y = map(t.x, TS_Min.x, TS_Max.x, 0, tft.height());	//   ... USB port at lower right
    #endif
    

    And then It Just Worked.

  • Kenmore 158 Needle LEDs: First Light

    With the boost converter mounted and the needle LEDs wired up:

     Kenmore 158 Needle Light - heatsink
    Kenmore 158 Needle Light – heatsink

    The Kenmore 158 sewing machine crash test dummy has plenty of light:

    Kenmore 158 LED Lighting - first light
    Kenmore 158 LED Lighting – first light

    Well, as long as you don’t mind the clashing color balance. The needle LEDs turned out warmer than I expected, but Mary says she can cope. I should build a set of warm-white LED strips when it’s time to refit her real sewing machine and add another boost supply to drive them at their rated current.

    Much to our relief, the two LEDs at the needle don’t cast offensively dark shadows:

    Kenmore 158 LED Lighting - detail
    Kenmore 158 LED Lighting – detail

    All in all, it looks pretty good.

  • Wider Borders in XFCE / Xubuntu

    A longstanding Xubuntu / XFCE UI problem has been single-pixel window borders that make click-and-drag resizing essentially impossible. The reason it’s a longstanding problem has been the developers’ unflinching response to any and all issues raised on the bug tracker:

    That discussion may be illuminating.

    I had never looked for the XFCE theme-building documentation (and, thus, never found any), because building a whole new theme would be a lot of work just to resize the damn borders. It should be feasible to tweak only the borders of an existing theme, but … I stalled.

    Repeatedly. On every single version of Xubuntu that’s come along.

    Fortunately, someone recently did the legwork and summarized the method, which I slightly adapted:

    cd /usr/share/themes/
    sudo cp -a Greybird-compact/ Greybird-wide
    cd Greybird-wide/xfwm4
    for f in bottom left right ; do sudo cp ../../Daloa/xfwm4/${f}* . ; done
    sudo sed -i -e 's/C0C0C0/CECECE/' *xpm
    sudo sed -i -e 's/A0A0FF/7C7C7C/' *xpm
    sudo sed -i -e 's/E0E0FF/E0E0E0/' *xpm
    

    The exact color mapping depends on which two themes you’re using. You can also specify GTK element colors, which seems like a better way to do it. Maybe next time.

    Apparently, the corresponding PNG files contain transparency information for the XPM files, but I haven’t bothered to investigate how that works or what might happen if I tweaked them.

    Then you select the new Graybird-wide theme and It Just Works.

    Sheesh & similar remarks…

  • Dual Monitors Redux

    My trusty 1050×1680 portrait monitor began resetting itself, which probably indicates failing capacitors in the power supply or logic board; eBay has capacitor kits, but it may not be worthwhile fixing the poor thing. I snagged a new 2560×1440 Dell U2713HM monitor, added a dual-Displayport PNY NVS310 video card, told Xubuntu 14.04LTS to use nVidia’s binary driver, and, somewhat to my astonishment, It Just Worked.

    The xrandr report:

    Screen 0: minimum 8 x 8, current 4000 x 2560, maximum 16384 x 16384
    DP-0 disconnected primary (normal left inverted right x axis y axis)
    DP-1 disconnected (normal left inverted right x axis y axis)
    DP-2 connected 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm
       2560x1440      60.0*+
       1920x1200      59.9
       1920x1080      60.0     59.9     50.0     24.0     60.1     60.0     50.0
       1680x1050      60.0
       1600x1200      60.0
       1280x1024      75.0     60.0
       1280x800       59.8
       1280x720       60.0     59.9     50.0
       1152x864       75.0
       1024x768       75.0     60.0
       800x600        75.0     60.3
       720x576        50.0     50.1
       720x480        59.9     60.1
       640x480        75.0     59.9     59.9
    DP-3 connected 1440x2560+2560+0 left (normal left inverted right x axis y axis) 597mm x 336mm
       2560x1440      60.0*+
       1920x1200      59.9
       1920x1080      60.0     59.9     50.0     24.0     60.1     60.0     50.0
       1680x1050      60.0
       1600x1200      60.0
       1280x1024      75.0     60.0
       1280x800       59.8
       1280x720       60.0     59.9     50.0
       1152x864       75.0
       1024x768       75.0     60.0
       800x600        75.0     60.3
       720x576        50.0     50.1
       720x480        59.9     60.1
       640x480        75.0     59.9     59.9
    

    Inexplicably, xsetwacom once again expects the "HEAD-0" parameter that was "DP1" the last time around:

    xsetwacom --verbose set "Wacom Graphire3 6x8 stylus" MapToOutput "HEAD-0"
    xsetwacom --verbose set "Wacom Graphire3 6x8 eraser" MapToOutput "HEAD-0"
    

    The new display presents crisp characters; seeing 140 source code lines at once is wonderful.