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

  • Dell Inspiron E1405 vs. Ubuntu 14.04LTS vs. Broadcom Drivers

    So the ancient Dell E1405 laptop on the Electronics Bench, connected to this-and-that, woke up without network connections. As in, right after booting, the link and activity lights jammed on solid, the usual eth0 device wasn’t there, WiFi was defunct, and nothing made any difference.

    After a bit of searching, the best summary of what to do appears on the Ubuntu forums. The gist of the story, so I need not search quite so much the next time, goes like this:

    The laptop uses the Broadcom BCM4401 Ethernet and BCM4311 WiFi chips, which require the non-free Broadcom firmware found in the linux-nonfree-firmware package. There’s a proprietary alternative in bcmwl-kernel-source that apparently works well for most Broadcom chips, but not this particular set.

    Guess which driver installed itself as part of the previous update?

    The key steps:

    sudo apt-get purge bcmwl-kernel-source
    egrep 'blacklist (b43|ssb)' /etc/modprobe.d/*
    ... then manually kill any files that appear ...
    

    Apparently that problem has been tripping people for at least the last four years. That this is the 14.04 Long Term Support version evidently has little to do with anything at all.

    While I was at it, I deleted all the nVidia packages that somehow installed themselves without my noticing; the laptop has Intel 945 integrated graphics hardware.

    I vaguely recall what I intended to do before this happened…

  • 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…

  • Fixing Ubuntu’s nVidia Driver Update Glitch

    So there’s been a conflict between Ubuntu’s kernel update procedure (which has trouble with non-GPL kernel modules) and the nVidia proprietary drivers (which you must use in order to Make Things Work). Ever since 14.04LTS came out, some-but-not-all kernel updates have produced anything from no problem at all to a totally broken system requiring esoteric manual tweakage that shouldn’t be expected of mere mortals.

    You know it’s a problem when one of the many bug reports starts out thusly:

    This bug affects 2593 people

    Bug Description

    **WARNING:** This bug has been widely reported and has *many* automatic subscribers. Please be considerate.

    The most recent update to my desktop box clobbered it hard enough that the landscape display didn’t start up properly and the portrait display wasn’t rotated. The same update to other boxes seems to have worked, but that may be a set of unwarranted assumptions; the boxes simply haven’t displayed any obvious symptoms.

    After having to fix this mess every now and again over the last year, this worked:

    sudo apt-get install --reinstall nvidia-331-uvm
    

    As nearly as I can tell, reinstalling any nVidia package that’s already installed simply retriggers the failing step, resulting in a clean and workable installation. There’s apparently something wrong with the Dynamic Kernel Module Support structure that works the second time around, but I have no idea (and little interest) about the details.

    However, that “fix” required this sequence:

    • Boot the rescue session from the Grub menu
    • Activate networking
    • Clean out any broken packages
    • Drop to a root shell prompt
    • Do the apt-get dance
    • Power off
    • Unplug the portrait montitor’s Displayport cable
    • Boot to the BIOS settings to force-start the landscape monitor
    • Power off
    • Reconnect the portrait monitor
    • Reboot into Xubuntu as usual
    • Reset the monitor positions
    • Reload the desktop backgrounds

    Now, at least, all that’s written down where I can refer to it the next time this happens… on a separate laptop, of course.

    This has been happening for nigh onto a year in what Ubuntu charmingly calls a “long term support” release.

  • 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?

  • Xubuntu vs. Gnome Keyring Redux

    Once again, another Xubuntu desktop box started having troubles with the Gnome keyring manager, with baffling symptoms including a request for a password you don’t know and forgetting passwords you’ve entered correctly.

    The solution, much as before, requires at least some of:

    • Auto-start Gnome services: Session & Startup -> Advanced -> ×
    • Find and delete the keyrings directory: this time it was ~/.gnome2/keyrings
    • Tweak the contents of /etc/xdg/autostart/gnome-keyring-pkcs11.desktop
    • Reboot that sucker
    • Enter passwords as needed, which should be The Last Time you must do that

    This keyring problem remains a problem after all these years, because … I haven’t a clue.

    At least now I have a list of things to try, which should might reduce the hassle next time around.

  • Kenmore 158 Motor Controller: Button Command Decoder

    Now that the sewing machine motor controller receives commands from the UI (or typed in on a console), it must decode them. The “parser” doesn’t amount to much, because the commands consist of exactly two characters wrapped in square brackets. For simplicity, if the format doesn’t match or the command isn’t exactly right, the decoder simply tosses it on the floor and moves on:

    void ParseCmd(char *pBuff) {
    
    	if ((CmdBuffer[0] != '[') || (CmdBuffer[3] != ']')) {
    		printf("** Bad cmd format: %s\r\n",CmdBuffer);
    		return;	
    	}
    
    	switch (CmdBuffer[1]) {
    	case 'N':							// needle park position
    		switch (CmdBuffer[2]) {
    		case 'u':
    			MotorDrive.ParkPosition = NS_UP;
    //			ParkNeedle(NS_UP);
    			break;
    		case 'a':
    			MotorDrive.ParkPosition = NS_NONE;
    			break;
    		case 'd':
    			MotorDrive.ParkPosition = NS_DOWN;
    //			ParkNeedle(NS_DOWN);
    			break;
    		default:
    			printf("** Bad Needle cmd: %s\r\n",CmdBuffer);
    		}
    		break;
    	case 'P':							// pedal mode
    		switch (CmdBuffer[2]) {
    		case 'r':
    			MotorDrive.PedalMode = PD_RUN;
    			break;
    		case '1':
    			MotorDrive.PedalMode = PD_SINGLE;
    			break;
    		case 'f':
    			MotorDrive.PedalMode = PD_FOLLOW;
    			break;
    		default:
    			printf("** Bad Pedal cmd: %s\r\n",CmdBuffer);
    		}
    		break;
    	case 'S':							// motor speed range
    		switch (CmdBuffer[2]) {
    		case 'h':
    			MotorDrive.SpeedRange = SPEED_HIGH;
    			PedalMaxClamp = PEDALMAX;
    			break;
    		case 'm':
    			MotorDrive.SpeedRange = SPEED_MEDIUM;
    			PedalMaxClamp = (3 * PEDALMAX) / 4;
    			break;
    		case 'l':
    			MotorDrive.SpeedRange = SPEED_LOW;
    			PedalMaxClamp = PEDALMAX / 2;
    			break;
    		default:
    			printf("** Bad Speed cmd: %s\r\n",CmdBuffer);
    		}
    		break;
    	default:
    		printf("** Bad command string: %s\r\n",CmdBuffer);
    	}
    	
    	return;
    }
    

    So much for recursive descent parser design theory, eh?

  • Kenmore 158 UI: Button Commands

    The button definition table now includes a string that becomes the button’s command to the sewing machine motor controller:

    #define MAXCMDLEN 2
    
    enum bstatus_t {BT_DISABLED,BT_UP,BT_DOWN};
    
    typedef void (*pBtnFn)(byte BID);		// button action function called when hit
    
    struct button_t {
    	byte ID;					// button identifier, 0 unused
    	byte Group;					// radio button group, 0 for none
    	byte Status;				// button status
    	word ulX,ulY;				// origin: upper left
    	word szX,szY;				// button image size
    	pBtnFn pAction;				// button function
    	char Cmd[MAXCMDLEN + 1];	// command string
    	char NameStem[9];			// button BMP file name - stem only
    };
    
    struct button_t Buttons[] = {
    	{ 1,	1, BT_UP,		  0,0,		 80,80,	DefaultAction,	"Nu",	"NdUp"},
    	{ 2,	1, BT_UP,		  0,80,		 80,80,	DefaultAction,	"Na",	"NdAny"},
    	{ 3,	1, BT_DOWN,		  0,160,	 80,80,	DefaultAction,	"Nd",	"NdDn"},
    
    	{ 4,	2, BT_DOWN,		 80,0,		120,80,	DefaultAction,	"Pr",	"PdRun"},
    	{ 5,	2, BT_UP,		 80,80,		120,80,	DefaultAction,	"P1",	"PdOne"},
    	{ 6,	2, BT_UP,		 80,160,	120,80,	DefaultAction,	"Pf",	"PdFol"},
    
    	{ 7,	3, BT_DOWN,		200,0,		 80,80,	DefaultAction,	"Sh",	"SpMax"},
    	{ 8,	3, BT_UP,		200,80,		 80,80,	DefaultAction,	"Sm",	"SpMed"},
    	{ 9,	3, BT_UP,		200,160,	 80,80,	DefaultAction,	"Sl",	"SpLow"},
    
    //	{10,	0, BT_UP,	311,0,		  8,8,	CountColor,	'\0',	'\0',	"Res"}
    };
    
    byte NumButtons = sizeof(Buttons) / sizeof(struct button_t);
    

    The default button handler now sends the button’s command string whenever it finds the button down after all the processing:

    #define TRACEACTION false
    
    void DefaultAction(byte BID) {
    
    byte i,BX;
    byte Group;
    
    	if (!BID) {											// not a valid ID
    		printf("** Button ID zero in DefaultAction\r\n");
    		return;
    	}
    
    	BX = FindButtonIndex(BID);
    	if (BX == NumButtons) {								// no button for that ID
    //		printf("** No table entry for ID: %d\r\n",BID);
    		return;
    	}
    
    #if TRACEACTION
    	printf("Default action: BID %d St %d -- ",BID,Buttons[BX].Status);
    #endif
    
    	if (Buttons[BX].Status == BT_DISABLED) {		// cannot do anything to disabled buttons
    #if TRACEACTION
    		printf("disabled\r\n");
    #endif
    		return;
    	}
    
    	Group = Buttons[BX].Group;
    
    	if (Group) {									// member of group?
    		if (Buttons[BX].Status == BT_DOWN) {		//  if down, remain that way
    #if TRACEACTION
    			printf("already down\r\n");
    #endif
    		}
    		else {										//  is up
    			for (i=0; i<NumButtons; i++) {			//   so unpush other buttons in group
    				if ((Buttons[i].Group == Group) && (Buttons[i].Status == BT_DOWN) && (i != BX)) {
    #if TRACEACTION
    					printf("release ID %d - ",Buttons[i].ID);
    #endif
    					Buttons[i].Status = BT_UP;
    					DrawButton(Buttons[i].ID,Buttons[i].Status);
    				}
    			}
    #if TRACEACTION
    			printf("push\r\n");
    #endif
    			Buttons[BX].Status = BT_DOWN;			//   and push this button down
    		}
    	}
    	else {											// not a group, so just toggle
    #if TRACEACTION
    		printf("toggle\r\n");
    #endif
    		Buttons[BX].Status = (Buttons[BX].Status == BT_DOWN) ? BT_UP : BT_DOWN;
    	}
    
    	DrawButton(BID,Buttons[BX].Status);
    
    	if (Buttons[BX].Status == BT_DOWN) {			// is this button now (or still) pressed?
    		SendCmd(Buttons[BX].Cmd);
    	}
    
    }
    

    That means the controller will see identical commands each time the button gets pressed, which doesn’t have any downsides. You could build an increment / decrement speed function without much trouble, although there’s still no way to display any returned values on the LCD.

    Working under the possibly unwarranted assumption that serial communications between the two Arduinos won’t encounter any errors, I just wrap the command string in a distinctive marker and send it off:

    void SendCmd(char *pCmd) {
    
    char Msg[MAXCMDLEN + 3];
    
    	strcpy(Msg,"[");
    	strcat(Msg,pCmd);
    	strcat(Msg,"]");
    
    	Serial.print("Cmd: ");							// copy to console
    	Serial.println(Msg);
    
    	Serial1.println(Msg);							// send command!
    
    }
    

    The Serial1 port runs at a nose-pickin’ 9600 baud, because the motor controller often gets wrapped up in what it’s doing. On the other paw, when the controller gets distracted, the operator will be feeding fabric past the needle at a pretty good clip and won’t have a finger to spare for the UI buttons, so it would probably work no matter what.

    That mismatch, however, allows the motor controller to babble on at length, without overruning the UI’s console output. This routine collects lines from the controller:

    char GetStatLine(void) {
    
    static byte Index = 0;
    char NewChar;
    
    	if (!Serial1.available()) {					// return if no chars in queue
    		return 0;
    	}
    
    	do {
    		NewChar = Serial1.read();
    		switch (NewChar) {
    		case '\r':								// end-of-line on CR
    			MCtlBuffer[Index] = 0;
    			Index = 0;
    			return strlen(MCtlBuffer);			// return from mid-loop
    			break;								//  unnecessary
    		case '\n':								// discard NL
    			break;
    		default:
    			MCtlBuffer[Index] = NewChar;		// store all others
    			Index += (Index < STATMAXLEN) ? 1 : 0;
    		}
    	} while (Serial1.available());
    
    	return 0;
    
    }
    

    A call in the main loop dumps each line after the terminating CR:

    char GetStatLine(void) {
    
    static byte Index = 0;
    char NewChar;
    
    	if (!Serial1.available()) {					// return if no chars in queue
    		return 0;
    	}
    
    	do {
    		NewChar = Serial1.read();
    		switch (NewChar) {
    		case '\r':								// end-of-line on CR
    			MCtlBuffer[Index] = 0;
    			Index = 0;
    			return strlen(MCtlBuffer);			// return from mid-loop
    			break;								//  unnecessary
    		case '\n':								// discard NL
    			break;
    		default:
    			MCtlBuffer[Index] = NewChar;		// store all others
    			Index += (Index < STATMAXLEN) ? 1 : 0;
    		}
    	} while (Serial1.available());
    
    	return 0;
    
    }
    

    Which produces output like this:

    Kenmore Model 158 User Interface
     Compiled: Jan 26 2015 at 15:33:52
    Ed Nisley - KE4ZNU
    TS... OK
    SD... OK
    LCD... should be active
    Cmd: [Nd]
    Cmd: [Pr]
    Cmd: [Sh]
    MC |** Bad command string: [--]
    MC |  540, 65535,   194
    MC |  610,     0,   194
    MC |  783,    55,   236
    MC | 1262,    84,   391
    MC | 1452,   116,   394
    MC | 1633,   123,   394
    MC | 1494,   132,   405
    MC | 1768,   126,   406
    MC | 1488,   126,   406
    MC | 1425,   137,   406
    MC | 1517,   132,   406
    MC | 1461,   126,   209
    MC |Coast: 1099
    MC |Parking Stop down: Done
    MC | stopped
    

    The “bad command string” isn’t actually an error. The first outbound line consists of [--] and a carriage return, which isn’t a valid command, just to make sure that the motor controller’s incoming serial port buffer doesn’t contain any junk. Obviously, I should add that string to the command decoder…