Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
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
The solid model shows two screws holding the PCB in place:
Arduino Mega PCB Mount
I decided to edge-clamp the board, rather than fuss with the built-in screws, just because 3D printing makes it so easy.
Of course, the UI needs a real case that will hold it at an angle, so as to make the LCD and touch screen more visible and convenient; this mount just keeps the PCB up off the conductive surface of the insulating board we’re using in lieu of a Real Sewing Platform.
This sewing machine project involves a lot of parts…
From a datalogger hanging on a string in the well pit, about three feet underground, in December:
Well Pit – 2014-12 – min size
The temperatures continue downward in January:
Well Pit – 2015-01 – min size
The corresponding attic air temperature record for January ends early:
Attic – Insulated Box – Maxell battery failure
When the air temperature dropped to +11 °F in the early hours of 17 January 2015, the well pit hit 35.5 °F. It was just over 35 °F in the wee hours of 29 January 2015, but the attic logger gave up as the battery voltage declined to 2.8 V.
Evidently, the new Maxell CR2032 lithium cells don’t do well in extreme cold. They’re rated to -20 °C = -4 °F, but that spec applies for a very low load that surely doesn’t include blinking a red LED.
I’ll take a look at that logger in a few days, then hack a pair of AA cells on the back if it’s dead again. Alkaline cells aren’t very good in cold weather, either, but they may have a better minimum voltage.
The Kenmore 158 sewing machine crash test dummy has plenty of 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:
The DC-DC boost power supply for the LED needle lights has four mounting holes, two completely blocked by the heatsink and the others against components with no clearance for screw heads, soooo …
3D printing to the rescue:
Boost converter – installed
Now that the hulking ET227 operates in saturation mode, I removed the blower to make room for the power supply. Two strips of double-stick foam tape fasten the holder to the removable tray inside the Dell GX270’s case.
It’s basically a rounded slab with recesses for the PCB and clearance for solder-side components:
Boost converter mount – as printed
The solid model shows the screw holes sitting just about tangent to the PCB recess:
XW029 Booster PCB Mount
That’s using the new OpenSCAD with length scales along each axis; they won’t quite replace my layout grid over the XY plane, but they certainly don’t require as much computation.
I knew my lifetime supply of self-tapping hex head 4-40 screws would come in handy for something:
Boost converter in mount
The program needs to know the PCB dimensions and how much clearance you want for the stuff hanging off the bottom:
PCBoard = [66,35,IntegerMultiple(1.8,ThreadThick)];
BottomParts = [[1.5,-1.0,0,0], // xyz offset of part envelope
[60.0,37.0,IntegerMultiple(3.0,ThreadThick)]]; // xyz envelope size (z should be generous)
That’s good enough for my simple needs.
The hole locations form a list-of-vectors that the code iterates through:
That’s the first occasion I’ve had to try iterating a list and It Just Worked; I must break the index habit. The newest OpenSCAD version has Python-ish list comprehensions which ought to come in handy for something.
The “Z coordinate” of each hole position gives its rotation, so I could snuggle them up a bit closer to the edge by forcing the proper polygon orientation. The square roots in the second two holes make them tangent to the corners of the PCB, rather than the sides, which wasn’t true for the first picture. Fortunately, the washer head of those screws turned out to be just big enough to capture the PCB anyway.
Given the data structures defining the buttons, this code in the main loop() detects a touch, identifies the corresponding button, and does what’s needed:
if (CleanTouch(&pt)) {
BID = FindHit(pt);
if (BID) {
HitButton(BID);
}
while(ts.touched()) // stall waiting for release
ts.getPoint();
}
The CleanTouch() function handles touch detection, cleanup, and rotation, delivering a coordinate that matches one of the LCD pixels. Given that you’re using a fingertip, errors caused by poor calibration or nonlinearities Just Don’t Matter.
This function matches that coordinate against the target region of each button, draws a white rectangle on the first matching button, and returns that button ID:
byte FindHit(TS_Point hit) {
byte i;
TS_Point ul,lr;
#define MARGIN 12
// printf("Hit test: (%d,%d)\r\n",hit.x,hit.y);
for (i=0; i<NumButtons ; i++) {
ul.x = Buttons[i].ulX + Buttons[i].szX/MARGIN;
ul.y = Buttons[i].ulY + Buttons[i].szY/MARGIN;
lr.x = Buttons[i].ulX + ((MARGIN - 1)*Buttons[i].szX)/MARGIN;
lr.y = Buttons[i].ulY + ((MARGIN - 1)*Buttons[i].szY)/MARGIN;
// printf(" i: %d BID: %d S: %d ul=(%d,%d) sz=(%d,%d)\r\n",
// i,Buttons[i].ID,Buttons[i].Status,ul.x,ul.y,lr.x,lr.y);
if ((hit.x >= ul.x && hit.x < lr.x) &&
(hit.y >= ul.y && hit.y <= lr.y)) {
// should test for being disabled and discard hit
// printf(" Hit i: %d ",i);
break;
}
}
if (i < NumButtons) {
tft.drawRect(ul.x,ul.y,lr.x-ul.x,lr.y-ul.y,ILI9341_WHITE);
return Buttons[i].ID;
}
else {
printf(" No hit!\r\n");
return 0;
}
}
You can enable as much debugging as you need by fiddling with the commented-out lines.
After some empirical fiddling, a non-sensitive margin of 1/12 the button size helped prevent bogus hits. There’s no real need to draw the target rectangle, other than for debugging:
Kenmore 158 UI buttons – hit target
The target shows the button graphics aren’t quite centered, because that’s how the ImageMagick script placed them while generating the shadow effect, but it still works surprisingly well. The next version of the buttons will center the graphics, specifically so I don’t have to explain what’s going on.
Because the margin is 1/12 the size of the button, it rounds off to zero for the tiny button in the upper right corner, so that the touch target includes the entire graphic.
The return value will be zero if the touch missed all the buttons, which is why a button ID can’t be zero.
Given the button ID, this function un-pushes the other button(s) in its radio button group, then pushes the new button:
byte HitButton(byte BID) {
byte i,BX;
byte Group;
if (!BID) // not a valid ID
return 0;
BX = FindButtonIndex(BID);
if (BX == NumButtons) // no button for that ID
return 0;
Group = Buttons[BX].Group;
// printf(" Press %d X: %d G: %d\r\n",BID,BX,Group);
// If in button group, un-push other buttons
if (Group) {
for (i=0; i<NumButtons; i++) {
if ((Group == Buttons[i].Group) && (BT_DOWN == Buttons[i].Status)) {
if (i == BX) { // it's already down, fake going up
Buttons[i].Status = BT_UP;
}
else { // un-push other down button(s)
// printf(" unpress %d X: %d \r\n",Buttons[i].ID);
Buttons[i].pAction(Buttons[i].ID);
}
}
}
}
Buttons[BX].pAction(BID);
return 1;
}
The ID validation shouldn’t be necessary, but you know how things go. A few messages in there would help debugging.
The default button action routine that I use for all the buttons just toggles the button’s Status and draws the new button graphic:
void DefaultAction(byte BID) {
byte i,BX;
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;
}
Buttons[BX].Status = (Buttons[BX].Status == BT_DOWN) ? BT_UP : BT_DOWN;
printf("Button %d hit, now %d\r\n",BID,Buttons[BX].Status);
DrawButton(BID,Buttons[BX].Status);
}
The little color indicator button has a slightly different routine to maintain a simple counter stepping through all ten resistor color codes in sequence:
void CountColor(byte BID) {
byte i,BX;
static byte Count = 0;
if (!BID) { // not a valid ID
printf("** Zero button ID\r\n");
return;
}
BX = FindButtonIndex(BID);
if (BX == NumButtons) { // no button for that ID
printf("** No entry for ID: %d\r\n",BID);
return;
}
Buttons[BX].Status = BT_DOWN; // this is always pressed
Count = (Count < 9) ? ++Count : 0; // bump counter & wrap
// printf("Indicator %d hit, now %d\r\n",BID,Count);
DrawButton(BID,Count);
}
The indicator “button” doesn’t go up when pressed and its function controls what’s displayed.
I think the button action function should have an additional parameter giving the next Status value, so that it knows what’s going on, thus eliminating the need to pre-push & redraw buttons in HitButton(), which really shouldn’t peer inside the button data.
It needs more work and will definitely change, but this gets things started.