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:

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:

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:

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…
Comments
4 responses to “Kenmore 158: Linearized Speed Control”
Now you’ve gone beyond the realm of most commercial products. They’ll just put in whatever slope is easiest and call it done. However, you’re actually evaluating the user experience and feedback, along with the physics of the process, the math behind it, and the amount of calculations your code has time to accomplish. That is real dedication to Getting It Right, which leads to the difference between a merely adequate product and one that’s really satisfying to use.
And it definitely secures me a spot near the inner edge of the Seventh Circle of Dweebdom. [grin]
I must measure the original rheostat control’s transfer function, which I should have done up front. If I’ve devoted all this effort to (unknowingly) duplicating that function, well, cue the Happy Dance…
because in commercial products the user can’t display the same amount of power.
As the saying goes: “When Momma ain’t happy, ain’t nobody happy!” [grin]