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.

The New Hotness

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