mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
new tft
This commit is contained in:
@@ -555,7 +555,7 @@
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>TFT Display - 2.8 inch and 2.4 inch ILI9341, 1.8 inch ST7735, 1.54 inch ST7789, 1.28 inch round GC9A01</description>
|
||||
<description>TFT Display - 2.8 inch and 2.4 inch ILI9341, 1.8 inch and 0.96 inch ST7735, 1.54 inch ST7789, 1.28 inch round GC9A01</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
|
||||
@@ -85,6 +85,14 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
public static final String[] PIN_NAMES_SD =
|
||||
new String[] {"SD_CS", "SD_MOSI", "SD_MISO", "SD_SCK"};
|
||||
|
||||
/**
|
||||
* The eight-pin row the 1.8" and the 1.54" share, and the same lines the 0.96" brings out. Two
|
||||
* different controllers behind one header: what a board of this size exposes is the four-wire SPI
|
||||
* interface plus reset, data/command and the backlight, and that does not vary with the driver.
|
||||
*/
|
||||
public static final String[] PIN_NAMES_SPI_8PIN =
|
||||
new String[] {"GND", "VCC", "SCL", "SDA", "RES", "DC", "CS", "BLK"};
|
||||
|
||||
public static Color TFT_RED = Color.decode("#C0392B");
|
||||
public static Color SCREEN_BG = Color.decode("#111111");
|
||||
public static Color GLASS_COLOR = Color.decode("#1A1A1A");
|
||||
@@ -169,11 +177,15 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
}
|
||||
|
||||
/**
|
||||
* Top edge of the board. Every variant carries its main header on the bottom edge, so the board
|
||||
* always stands above control point zero and the offset is always measured up from the bottom.
|
||||
* Top edge of the board. Most variants carry their main header on the bottom edge and stand above
|
||||
* control point zero, so the offset is measured up from the bottom; the 0.96" has its row on the
|
||||
* top edge and hangs below it instead.
|
||||
*/
|
||||
private double getBoardY(double y) {
|
||||
Controller controller = getController();
|
||||
if (controller.isHeaderAtTop()) {
|
||||
return y - px(controller.getHeaderOffsetMm());
|
||||
}
|
||||
return y - px(controller.getBoardLengthMm() - controller.getHeaderOffsetMm());
|
||||
}
|
||||
|
||||
@@ -190,15 +202,18 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
relativeOffsets[i][1] = 0;
|
||||
}
|
||||
|
||||
// The second row faces the first across the board -- always above it, since the main header is
|
||||
// always on the bottom edge -- so its distance from control point zero is what the two offsets
|
||||
// leave of the length. Both rows are centred on the width, which puts the shorter one in by
|
||||
// half the difference between the spans; that horizontal placement is the one figure of this
|
||||
// header that is not measured.
|
||||
// The second row faces the first across the board, so its distance from control point zero is
|
||||
// what the two offsets leave of the length -- above the first row on a board headed at the
|
||||
// bottom and below it on one headed at the top. Both rows are centred on the width, which puts
|
||||
// the shorter one in by half the difference between the spans; that horizontal placement is the
|
||||
// one figure of this header that is not measured.
|
||||
if (secondaryCount > 0) {
|
||||
double indent = (primaryCount - secondaryCount) * spacing / 2.0;
|
||||
double across = -px(controller.getBoardLengthMm() - controller.getHeaderOffsetMm()
|
||||
double across = px(controller.getBoardLengthMm() - controller.getHeaderOffsetMm()
|
||||
- controller.getSecondaryHeaderOffsetMm());
|
||||
if (!controller.isHeaderAtTop()) {
|
||||
across = -across;
|
||||
}
|
||||
for (int i = 0; i < secondaryCount; i++) {
|
||||
relativeOffsets[primaryCount + i][0] = indent + i * spacing;
|
||||
relativeOffsets[primaryCount + i][1] = across;
|
||||
@@ -285,15 +300,18 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
double holeInset = px(controller.getHoleInsetMm());
|
||||
double headerSideInset = px(controller.getHeaderSideHoleInsetMm());
|
||||
double holeSize = px(controller.getHoleSizeMm());
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + holeInset, holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInset, boardY + holeInset,
|
||||
// The deeper inset, where a board has one, belongs to the pair sharing the header's edge.
|
||||
double topInset = controller.isHeaderAtTop() ? headerSideInset : holeInset;
|
||||
double bottomInset = controller.isHeaderAtTop() ? holeInset : headerSideInset;
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + topInset, holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInset, boardY + topInset,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset, boardY + boardH - bottomInset,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInset,
|
||||
boardY + boardH - headerSideInset, holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInset,
|
||||
boardY + boardH - headerSideInset, holeSize);
|
||||
bandTop = boardY + holeInset;
|
||||
bandBottom = boardY + boardH - headerSideInset;
|
||||
boardY + boardH - bottomInset, holeSize);
|
||||
bandTop = boardY + topInset;
|
||||
bandBottom = boardY + boardH - bottomInset;
|
||||
}
|
||||
|
||||
double screenW = px(controller.getScreenWidthMm());
|
||||
@@ -310,23 +328,33 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
screenH);
|
||||
} else {
|
||||
double bandCentre = (bandTop + bandBottom) / 2.0;
|
||||
double screenX;
|
||||
double screenY;
|
||||
if (controller.hasGlass()) {
|
||||
double glassH = px(controller.getGlassLengthMm());
|
||||
Glass glass = controller.getGlass();
|
||||
double glassH = px(glass.getLengthMm());
|
||||
double glassW = glass.spansTheBoard() ? boardW : px(glass.getWidthMm());
|
||||
double glassX = glass.spansTheBoard() ? boardX
|
||||
: boardX + boardW - px(glass.getRightInsetMm()) - glassW;
|
||||
double glassY = bandCentre - glassH / 2.0;
|
||||
g2d.setColor(GLASS_COLOR);
|
||||
g2d.fill(new Rectangle2D.Double(boardX, glassY, boardW, glassH));
|
||||
// The lit area is centred in the glass unless the variant measured it, which the 2.4"
|
||||
// does: its panel carries the driver's bonding region along the bottom, so the lit area
|
||||
// sits high in the frame and centring it would drop it 5 mm.
|
||||
g2d.fill(new Rectangle2D.Double(glassX, glassY, glassW, glassH));
|
||||
|
||||
// The lit area belongs to the panel rather than to the board, so it is centred in the
|
||||
// glass -- which is the same thing as centring it on the board for every variant whose
|
||||
// glass spans the board, and is not for the 0.96", whose panel is pushed to one side.
|
||||
// It is centred vertically too unless the variant measured it, which the 2.4" does: its
|
||||
// panel carries the driver's bonding region along the bottom, so the lit area sits high
|
||||
// in the frame and centring it would drop it 5 mm.
|
||||
screenX = glassX + (glassW - screenW) / 2.0;
|
||||
screenY = controller.hasMeasuredScreenTop() ? boardY + px(controller.getScreenTopMm())
|
||||
: glassY + (glassH - screenH) / 2.0;
|
||||
} else {
|
||||
screenX = boardX + (boardW - screenW) / 2.0;
|
||||
screenY = controller.hasMeasuredScreenTop() ? boardY + px(controller.getScreenTopMm())
|
||||
: bandCentre - screenH / 2.0;
|
||||
}
|
||||
panel = new Rectangle2D.Double(boardX + (boardW - screenW) / 2.0, screenY, screenW,
|
||||
screenH);
|
||||
panel = new Rectangle2D.Double(screenX, screenY, screenW, screenH);
|
||||
}
|
||||
|
||||
g2d.setColor(SCREEN_BG);
|
||||
@@ -349,13 +377,14 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
// The names go in the strip between the pin row and the panel, lying flat along the row as
|
||||
// the Nokia's do: the strip is 3.5 mm deep on the two smallest boards and a label stood on
|
||||
// end needs 4.7 mm. The row is on the bottom edge on every variant, so the strip, and the
|
||||
// names in it, are always above the pins.
|
||||
// names in it, are above the pins on a board headed at the bottom and below them on the
|
||||
// 0.96", which is headed at the top.
|
||||
//
|
||||
// Only the primary row is named. The SD row has the glass a couple of millimetres from it and
|
||||
// nothing but board edge on its other side, so there is no strip to print in; those four pins
|
||||
// are left to their tooltips, as the matrix's covered rows are.
|
||||
drawFlatRowPinLabels(g2d, x, y, getRelativeOffsets(), 0, controller.getPinNames().length,
|
||||
false, SILK_COLOR);
|
||||
controller.isHeaderAtTop(), SILK_COLOR);
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
@@ -402,8 +431,10 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
// The 2.8"'s micro-SD socket comes out on a four-pin row of its own, the way the 2.4"'s does,
|
||||
// on the top edge opposite the main header. It keeps the board's own 3 mm offset rather than
|
||||
// the 2.4"'s 2 mm, each board putting its second row as far in as its first.
|
||||
ILI9341_2_8("2.8\" ILI9341 240x320 (Touch + SD)", 50.0d, 86.0d, 43.2d, 57.6d, 69.1d, 3.0d,
|
||||
3.0d, 3.0d, 0d, PIN_NAMES_ILI9341, 0d, PIN_NAMES_SD, 3.0d, 3.0d),
|
||||
ILI9341_2_8("2.8\" ILI9341 240x320 (Touch + SD)", 50.0d, 86.0d, 43.2d, 57.6d, new Glass(69.1d),
|
||||
3.0d, 3.0d, 3.0d, 0d, PIN_NAMES_ILI9341, 0d, PIN_NAMES_SD, 3.0d, 3.0d, false),
|
||||
// Four of the six are headed at the top; the two ILI9341s and the round board are not. There is
|
||||
// no default to fall back on, so every constant states its own edge -- see the plan's 12.2.3.
|
||||
// The one board here measured rather than derived throughout, and the only one with a second
|
||||
// header. Four of its figures do not behave like the others': the 14-pin row is on the bottom
|
||||
// edge, the lit area is measured 9.26 mm down from the top rather than centred in the glass,
|
||||
@@ -412,23 +443,33 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
// row. The glass keeps the family's rule, centred between the hole rows, which the deeper pair
|
||||
// puts at 6.50 to 66.76 mm: 2.76 mm of frame above the lit area and 8.54 mm below it, the
|
||||
// asymmetry being the driver's bonding region along the bottom of the panel.
|
||||
ILI9341_2_4("2.4\" ILI9341 240x320 (Touch + SD)", 42.72d, 77.18d, 36.72d, 48.96d, 60.26d, 2.0d,
|
||||
3.0d, 3.0d, 0d, PIN_NAMES_ILI9341, 9.26d, PIN_NAMES_SD, 2.0d, 6.92d),
|
||||
ST7735_1_8("1.8\" ST7735 128x160", 34.0d, 45.8d, 28.56d, 35.7d, 0d, 1.5d, 3.0d, 3.0d, 0d,
|
||||
new String[] {"GND", "VCC", "SCK", "SDA", "RES", "DC", "CS", "BL"}),
|
||||
ST7789_1_54("1.54\" ST7789 240x240", 32.0d, 43.72d, 27.66d, 27.66d, 33.7d, 1.5d, 2.5d, 2.0d,
|
||||
0d,
|
||||
new String[] {"GND", "VCC", "SCL", "SDA", "RES", "DC", "CS", "BLK"}),
|
||||
GC9A01_1_28("1.28\" GC9A01 240x240 Round", 38.0d, 45.5d, 32.5d, 32.5d, 0d, 1.76d, 0d, 0d,
|
||||
ILI9341_2_4("2.4\" ILI9341 240x320 (Touch + SD)", 42.72d, 77.18d, 36.72d, 48.96d,
|
||||
new Glass(60.26d), 2.0d, 3.0d, 3.0d, 0d, PIN_NAMES_ILI9341, 9.26d, PIN_NAMES_SD, 2.0d,
|
||||
6.92d, false),
|
||||
// The one board in the family whose header is on the top edge, and the only one whose panel
|
||||
// does not span the board: a 23.7 x 12.8 mm bezel pushed into the right-hand side, 2 mm from
|
||||
// that edge and 4.3 mm from the other, with the driver's circuitry filling the bare L the
|
||||
// offset leaves. Its lit area is landscape where every other board's is portrait, it is
|
||||
// measured rather than derived, and it is centred in the bezel with a 1 mm frame all round.
|
||||
// The bezel is offset only across the board; down it, it keeps the family's rule and sits
|
||||
// centred, which on a board whose holes share one inset is the same as centred in the PCB.
|
||||
ST7735_0_96("0.96\" ST7735 80x160", 30.0d, 24.0d, 21.7d, 10.8d,
|
||||
new Glass(12.8d, 23.7d, 2.0d), 1.44d, 2.0d, 2.0d, 0d, PIN_NAMES_SPI_8PIN, 0d, null, 0d,
|
||||
2.0d, true),
|
||||
ST7735_1_8("1.8\" ST7735 128x160", 34.0d, 45.8d, 28.56d, 35.7d, null, 1.5d, 3.0d, 3.0d, 0d,
|
||||
PIN_NAMES_SPI_8PIN, true),
|
||||
ST7789_1_54("1.54\" ST7789 240x240", 32.0d, 43.72d, 27.66d, 27.66d, new Glass(33.7d), 1.5d,
|
||||
2.5d, 2.0d, 0d, PIN_NAMES_SPI_8PIN, true),
|
||||
GC9A01_1_28("1.28\" GC9A01 240x240 Round", 38.0d, 45.5d, 32.5d, 32.5d, null, 1.76d, 0d, 0d,
|
||||
22.9d,
|
||||
new String[] {"RST", "CS", "DC", "SDA", "SCL", "GND", "VCC"});
|
||||
new String[] {"RST", "CS", "DC", "SDA", "SCL", "GND", "VCC"}, false);
|
||||
|
||||
private final String label;
|
||||
private final double boardWidthMm;
|
||||
private final double boardLengthMm;
|
||||
private final double screenWidthMm;
|
||||
private final double screenLengthMm;
|
||||
private final double glassLengthMm;
|
||||
private final Glass glass;
|
||||
private final double headerOffsetMm;
|
||||
private final double holeInsetMm;
|
||||
private final double holeSizeMm;
|
||||
@@ -438,26 +479,27 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
private final String[] secondaryPinNames;
|
||||
private final double secondaryHeaderOffsetMm;
|
||||
private final double headerSideHoleInsetMm;
|
||||
private final boolean headerAtTop;
|
||||
|
||||
/** For a board whose lit area is centred and whose pins all leave on one row. */
|
||||
Controller(String label, double boardWidthMm, double boardLengthMm, double screenWidthMm,
|
||||
double screenLengthMm, double glassLengthMm, double headerOffsetMm, double holeInsetMm,
|
||||
double holeSizeMm, double tabWidthMm, String[] pinNames) {
|
||||
this(label, boardWidthMm, boardLengthMm, screenWidthMm, screenLengthMm, glassLengthMm,
|
||||
headerOffsetMm, holeInsetMm, holeSizeMm, tabWidthMm, pinNames, 0d, null, 0d, holeInsetMm);
|
||||
double screenLengthMm, Glass glass, double headerOffsetMm, double holeInsetMm,
|
||||
double holeSizeMm, double tabWidthMm, String[] pinNames, boolean headerAtTop) {
|
||||
this(label, boardWidthMm, boardLengthMm, screenWidthMm, screenLengthMm, glass, headerOffsetMm,
|
||||
holeInsetMm, holeSizeMm, tabWidthMm, pinNames, 0d, null, 0d, holeInsetMm, headerAtTop);
|
||||
}
|
||||
|
||||
Controller(String label, double boardWidthMm, double boardLengthMm, double screenWidthMm,
|
||||
double screenLengthMm, double glassLengthMm, double headerOffsetMm, double holeInsetMm,
|
||||
double screenLengthMm, Glass glass, double headerOffsetMm, double holeInsetMm,
|
||||
double holeSizeMm, double tabWidthMm, String[] pinNames, double screenTopMm,
|
||||
String[] secondaryPinNames, double secondaryHeaderOffsetMm,
|
||||
double headerSideHoleInsetMm) {
|
||||
String[] secondaryPinNames, double secondaryHeaderOffsetMm, double headerSideHoleInsetMm,
|
||||
boolean headerAtTop) {
|
||||
this.label = label;
|
||||
this.boardWidthMm = boardWidthMm;
|
||||
this.boardLengthMm = boardLengthMm;
|
||||
this.screenWidthMm = screenWidthMm;
|
||||
this.screenLengthMm = screenLengthMm;
|
||||
this.glassLengthMm = glassLengthMm;
|
||||
this.glass = glass;
|
||||
this.headerOffsetMm = headerOffsetMm;
|
||||
this.holeInsetMm = holeInsetMm;
|
||||
this.holeSizeMm = holeSizeMm;
|
||||
@@ -467,6 +509,7 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
this.secondaryPinNames = secondaryPinNames;
|
||||
this.secondaryHeaderOffsetMm = secondaryHeaderOffsetMm;
|
||||
this.headerSideHoleInsetMm = headerSideHoleInsetMm;
|
||||
this.headerAtTop = headerAtTop;
|
||||
}
|
||||
|
||||
@Override public String toString() { return label; }
|
||||
@@ -474,7 +517,8 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
public double getBoardLengthMm() { return boardLengthMm; }
|
||||
public double getScreenWidthMm() { return screenWidthMm; }
|
||||
public double getScreenLengthMm() { return screenLengthMm; }
|
||||
public double getGlassLengthMm() { return glassLengthMm; }
|
||||
/** The dark panel, or {@code null} on a board whose lit area is all there is to draw. */
|
||||
public Glass getGlass() { return glass; }
|
||||
public double getHeaderOffsetMm() { return headerOffsetMm; }
|
||||
public double getHoleSizeMm() { return holeSizeMm; }
|
||||
public String[] getPinNames() { return pinNames; }
|
||||
@@ -494,7 +538,14 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
|
||||
public boolean hasMountingHoles() { return holeSizeMm > 0; }
|
||||
|
||||
public boolean hasGlass() { return glassLengthMm > 0; }
|
||||
public boolean hasGlass() { return glass != null; }
|
||||
|
||||
/**
|
||||
* True where the main row leaves by the top edge, so the board hangs below it. Four of the six
|
||||
* do; see the plan's section 12.2.3 for why the family is not uniform and why no constant may
|
||||
* leave this to a default.
|
||||
*/
|
||||
public boolean isHeaderAtTop() { return headerAtTop; }
|
||||
|
||||
/** Width of the tab the header sits on; meaningful only for a round board. */
|
||||
public double getTabWidthMm() { return tabWidthMm; }
|
||||
@@ -518,4 +569,42 @@ public class TFTDisplay extends AbstractMakerBoard {
|
||||
public double getSecondaryHeaderOffsetMm() { return secondaryHeaderOffsetMm; }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The dark panel bonded to the front of the board, which is not the same thing as the lit area:
|
||||
* it is the glass the lit area sits in, and the frame it leaves around it is what a reader sees
|
||||
* as the display's border. Every panel is centred between its board's mounting holes, so what
|
||||
* varies is how far across the board it reaches. Carried as an object rather than as more figures
|
||||
* on {@link Controller} because only one board so far needs more than its height, and a
|
||||
* constructor that already takes fifteen arguments should not take seventeen.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public static class Glass {
|
||||
|
||||
private final double lengthMm;
|
||||
private final double widthMm;
|
||||
private final double rightInsetMm;
|
||||
|
||||
/** A panel that spans the board. */
|
||||
public Glass(double lengthMm) {
|
||||
this(lengthMm, 0d, 0d);
|
||||
}
|
||||
|
||||
public Glass(double lengthMm, double widthMm, double rightInsetMm) {
|
||||
this.lengthMm = lengthMm;
|
||||
this.widthMm = widthMm;
|
||||
this.rightInsetMm = rightInsetMm;
|
||||
}
|
||||
|
||||
public double getLengthMm() { return lengthMm; }
|
||||
|
||||
/** Zero where the panel is as wide as the board, which is every variant but the 0.96". */
|
||||
public double getWidthMm() { return widthMm; }
|
||||
|
||||
/** How far the panel stops short of the right edge; meaningful only where it is narrower. */
|
||||
public double getRightInsetMm() { return rightInsetMm; }
|
||||
|
||||
public boolean spansTheBoard() { return widthMm <= 0; }
|
||||
}
|
||||
}
|
||||
|
||||
+132
-52
@@ -91,6 +91,7 @@ public class TFTDisplayTest {
|
||||
}
|
||||
Assert.assertEquals(14, Controller.ILI9341_2_8.getPinNames().length);
|
||||
Assert.assertEquals(14, Controller.ILI9341_2_4.getPinNames().length);
|
||||
Assert.assertEquals(8, Controller.ST7735_0_96.getPinNames().length);
|
||||
Assert.assertEquals(8, Controller.ST7735_1_8.getPinNames().length);
|
||||
Assert.assertEquals(8, Controller.ST7789_1_54.getPinNames().length);
|
||||
Assert.assertEquals(7, Controller.GC9A01_1_28.getPinNames().length);
|
||||
@@ -127,21 +128,26 @@ public class TFTDisplayTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Three boards, two controllers, one header: what a module of this size brings out is the
|
||||
* four-wire SPI interface plus reset, data/command and the backlight, and that does not vary with
|
||||
* the driver behind it. Shared by identity rather than by three copies, so that a correction to
|
||||
* one reaches all of them.
|
||||
*/
|
||||
@Test
|
||||
public void pinNamesComeFromTheController() {
|
||||
TFTDisplay display = of(Controller.ST7735_1_8);
|
||||
String[] expected = new String[] {"GND", "VCC", "SCK", "SDA", "RES", "DC", "CS", "BL"};
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
Assert.assertEquals("pin " + i, expected[i], display.getControlPointNodeName(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void squareBoardPinNamesComeFromTheController() {
|
||||
TFTDisplay display = of(Controller.ST7789_1_54);
|
||||
public void theSmallBoardsShareOnePinArray() {
|
||||
Controller[] small =
|
||||
{Controller.ST7735_0_96, Controller.ST7735_1_8, Controller.ST7789_1_54};
|
||||
String[] expected = new String[] {"GND", "VCC", "SCL", "SDA", "RES", "DC", "CS", "BLK"};
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
Assert.assertEquals("pin " + i, expected[i], display.getControlPointNodeName(i));
|
||||
|
||||
for (Controller controller : small) {
|
||||
Assert.assertSame(controller.toString(), TFTDisplay.PIN_NAMES_SPI_8PIN,
|
||||
controller.getPinNames());
|
||||
TFTDisplay display = of(controller);
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
Assert.assertEquals(controller + " pin " + i, expected[i],
|
||||
display.getControlPointNodeName(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +179,8 @@ public class TFTDisplayTest {
|
||||
* The SD row faces the 14-pin row across the board and is centred on the width the way the longer
|
||||
* row is. Both are worth pinning: the across-the-board figure is what the two offsets leave of
|
||||
* the length rather than a measurement, and the horizontal placement is the one figure of this
|
||||
* header that was not supplied at all. Both boards that carry one put it on the top edge, above
|
||||
* the main row.
|
||||
* header that was not supplied at all. Both boards that carry one are headed at the bottom, so
|
||||
* their second row is on the top edge.
|
||||
*/
|
||||
@Test
|
||||
public void theSdRowStandsOffTheOppositeEdge() {
|
||||
@@ -187,12 +193,18 @@ public class TFTDisplayTest {
|
||||
int primaryCount = controller.getPinNames().length;
|
||||
|
||||
Point2D firstSd = display.getControlPoint(primaryCount);
|
||||
Assert.assertEquals(controller + " SD row offset from the top edge",
|
||||
controller.getSecondaryHeaderOffsetMm(), mm(firstSd.getY() - board.getY()), 0.01d);
|
||||
double fromItsEdge = controller.isHeaderAtTop() ? board.getMaxY() - firstSd.getY()
|
||||
: firstSd.getY() - board.getY();
|
||||
Assert.assertEquals(controller + " SD row offset from its own edge",
|
||||
controller.getSecondaryHeaderOffsetMm(), mm(fromItsEdge), 0.01d);
|
||||
|
||||
double between = controller.isHeaderAtTop()
|
||||
? firstSd.getY() - display.getControlPoint(0).getY()
|
||||
: display.getControlPoint(0).getY() - firstSd.getY();
|
||||
Assert.assertEquals(controller + " rows are not on opposite edges",
|
||||
controller.getBoardLengthMm() - controller.getHeaderOffsetMm()
|
||||
- controller.getSecondaryHeaderOffsetMm(),
|
||||
mm(display.getControlPoint(0).getY() - firstSd.getY()), 0.01d);
|
||||
mm(between), 0.01d);
|
||||
|
||||
Point2D lastSd = display.getControlPoint(display.getControlPointCount() - 1);
|
||||
Assert.assertEquals(controller + " SD row is not centred across the board",
|
||||
@@ -248,14 +260,12 @@ public class TFTDisplayTest {
|
||||
}
|
||||
double holeRadius = px(controller.getHoleSizeMm()) / 2.0;
|
||||
double boardH = px(controller.getBoardLengthMm());
|
||||
double bandTop = px(controller.getHoleInsetMm());
|
||||
double bandBottom = boardH - px(controller.getHeaderSideHoleInsetMm());
|
||||
double bandTop = px(topInsetOf(controller));
|
||||
double bandBottom = boardH - px(bottomInsetOf(controller));
|
||||
|
||||
double bandCentre = (bandTop + bandBottom) / 2.0;
|
||||
double panelH = controller.hasGlass() ? px(controller.getGlassLengthMm())
|
||||
: px(controller.getScreenLengthMm());
|
||||
double panelTop = bandCentre - panelH / 2.0;
|
||||
double panelBottom = bandCentre + panelH / 2.0;
|
||||
double panelH = panelLengthOf(controller);
|
||||
double panelTop = panelTopOf(controller);
|
||||
double panelBottom = panelTop + panelH;
|
||||
|
||||
Assert.assertTrue(controller + " panel overlaps the holes on its top edge",
|
||||
panelTop >= bandTop + holeRadius);
|
||||
@@ -284,12 +294,14 @@ public class TFTDisplayTest {
|
||||
double inset = px(controller.getHoleInsetMm());
|
||||
double headerSide = px(controller.getHeaderSideHoleInsetMm());
|
||||
double size = px(controller.getHoleSizeMm());
|
||||
double topInset = controller.isHeaderAtTop() ? headerSide : inset;
|
||||
double bottomInset = controller.isHeaderAtTop() ? inset : headerSide;
|
||||
|
||||
Rectangle2D[] holes = new Rectangle2D[] {
|
||||
hole(board.getX() + inset, board.getY() + inset, size),
|
||||
hole(board.getMaxX() - inset, board.getY() + inset, size),
|
||||
hole(board.getX() + inset, board.getMaxY() - headerSide, size),
|
||||
hole(board.getMaxX() - inset, board.getMaxY() - headerSide, size)};
|
||||
hole(board.getX() + inset, board.getY() + topInset, size),
|
||||
hole(board.getMaxX() - inset, board.getY() + topInset, size),
|
||||
hole(board.getX() + inset, board.getMaxY() - bottomInset, size),
|
||||
hole(board.getMaxX() - inset, board.getMaxY() - bottomInset, size)};
|
||||
|
||||
for (int i = 0; i < display.getControlPointCount(); i++) {
|
||||
Point2D p = display.getControlPoint(i);
|
||||
@@ -307,6 +319,32 @@ public class TFTDisplayTest {
|
||||
return new Rectangle2D.Double(cx - size / 2.0, cy - size / 2.0, size, size);
|
||||
}
|
||||
|
||||
private static double topInsetOf(Controller controller) {
|
||||
return controller.isHeaderAtTop() ? controller.getHeaderSideHoleInsetMm()
|
||||
: controller.getHoleInsetMm();
|
||||
}
|
||||
|
||||
private static double bottomInsetOf(Controller controller) {
|
||||
return controller.isHeaderAtTop() ? controller.getHoleInsetMm()
|
||||
: controller.getHeaderSideHoleInsetMm();
|
||||
}
|
||||
|
||||
/** The dark panel where there is one, and the lit area alone where there is not. */
|
||||
private static double panelLengthOf(Controller controller) {
|
||||
return px(controller.hasGlass() ? controller.getGlass().getLengthMm()
|
||||
: controller.getScreenLengthMm());
|
||||
}
|
||||
|
||||
/** Mirrors the drawing rule: every panel is centred between the hole rows. */
|
||||
private static double panelTopOf(Controller controller) {
|
||||
double bandTop = controller.hasMountingHoles() ? px(topInsetOf(controller))
|
||||
: px(controller.getHeaderOffsetMm());
|
||||
double bandBottom = controller.hasMountingHoles()
|
||||
? px(controller.getBoardLengthMm() - bottomInsetOf(controller))
|
||||
: px(controller.getBoardLengthMm());
|
||||
return (bandTop + bandBottom) / 2.0 - panelLengthOf(controller) / 2.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The panel has to leave the pin row uncovered. It is placed between the hole rows rather than
|
||||
* from an edge, so a long panel on a board whose holes sit close to the edges can reach back over
|
||||
@@ -321,17 +359,16 @@ public class TFTDisplayTest {
|
||||
continue;
|
||||
}
|
||||
double boardH = controller.getBoardLengthMm();
|
||||
double bandTop = controller.hasMountingHoles() ? controller.getHoleInsetMm()
|
||||
: controller.getHeaderOffsetMm();
|
||||
double bandBottom = controller.hasMountingHoles()
|
||||
? boardH - controller.getHeaderSideHoleInsetMm() : boardH;
|
||||
double panelH = controller.hasGlass() ? controller.getGlassLengthMm()
|
||||
: controller.getScreenLengthMm();
|
||||
double panelTop = (bandTop + bandBottom) / 2.0 - panelH / 2.0;
|
||||
double panelBottom = panelTop + panelH;
|
||||
double panelTop = mm(panelTopOf(controller));
|
||||
double panelBottom = panelTop + mm(panelLengthOf(controller));
|
||||
|
||||
Assert.assertTrue(controller + " panel covers the main row",
|
||||
panelBottom < boardH - controller.getHeaderOffsetMm());
|
||||
double mainRow = controller.isHeaderAtTop() ? controller.getHeaderOffsetMm()
|
||||
: boardH - controller.getHeaderOffsetMm();
|
||||
if (controller.isHeaderAtTop()) {
|
||||
Assert.assertTrue(controller + " panel covers the main row", panelTop > mainRow);
|
||||
} else {
|
||||
Assert.assertTrue(controller + " panel covers the main row", panelBottom < mainRow);
|
||||
}
|
||||
if (controller.hasSecondaryHeader()) {
|
||||
Assert.assertTrue(controller + " panel covers the SD row",
|
||||
panelTop > controller.getSecondaryHeaderOffsetMm());
|
||||
@@ -355,11 +392,8 @@ public class TFTDisplayTest {
|
||||
continue;
|
||||
}
|
||||
|
||||
double boardH = controller.getBoardLengthMm();
|
||||
double glassH = controller.getGlassLengthMm();
|
||||
double glassTop =
|
||||
(controller.getHoleInsetMm() + boardH - controller.getHeaderSideHoleInsetMm()) / 2.0
|
||||
- glassH / 2.0;
|
||||
double glassH = controller.getGlass().getLengthMm();
|
||||
double glassTop = mm(panelTopOf(controller));
|
||||
double screenTop = controller.getScreenTopMm();
|
||||
|
||||
Assert.assertTrue(controller + " lit area starts above its glass", screenTop >= glassTop);
|
||||
@@ -370,6 +404,39 @@ public class TFTDisplayTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The 0.96" is the only board whose panel does not span its width: a 23.7 mm bezel on a 30 mm
|
||||
* board, pushed to the right so that the driver's circuitry has the bare L it leaves. That offset
|
||||
* is across the board only -- down it the bezel is centred like every other variant's -- and it
|
||||
* is a measurement rather than a derivation, since centring it across the board too would move it
|
||||
* 1.15 mm. The lit area is centred inside it with a 1 mm frame on all four sides, which the
|
||||
* drawing gets for free once the glass is placed, and which would silently break if the lit area
|
||||
* went back to being centred on the board.
|
||||
*/
|
||||
@Test
|
||||
public void theOffsetPanelIsPlacedFromTheRightEdge() {
|
||||
for (Controller controller : Controller.values()) {
|
||||
Assert.assertEquals(controller + " panel spans the board",
|
||||
controller != Controller.ST7735_0_96,
|
||||
!controller.hasGlass() || controller.getGlass().spansTheBoard());
|
||||
}
|
||||
|
||||
Controller controller = Controller.ST7735_0_96;
|
||||
TFTDisplay.Glass glass = controller.getGlass();
|
||||
|
||||
double leftMargin = controller.getBoardWidthMm() - glass.getRightInsetMm() - glass.getWidthMm();
|
||||
Assert.assertEquals("left margin", 4.3d, leftMargin, 0.01d);
|
||||
Assert.assertTrue("the bezel is where centring would have put it",
|
||||
Math.abs(leftMargin - (controller.getBoardWidthMm() - glass.getWidthMm()) / 2.0) > 0.1d);
|
||||
|
||||
Assert.assertEquals("frame left and right", 1.0d,
|
||||
(glass.getWidthMm() - controller.getScreenWidthMm()) / 2.0, 0.01d);
|
||||
Assert.assertEquals("frame top and bottom", 1.0d,
|
||||
(glass.getLengthMm() - controller.getScreenLengthMm()) / 2.0, 0.01d);
|
||||
Assert.assertFalse("its lit area is measured, not derived from a centred glass",
|
||||
controller.hasMeasuredScreenTop());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mountingHolesSitInsideTheBoard() {
|
||||
for (Controller controller : Controller.values()) {
|
||||
@@ -394,9 +461,10 @@ public class TFTDisplayTest {
|
||||
*/
|
||||
@Test
|
||||
public void mountingHoleFiguresAreEachBoardsOwn() {
|
||||
double[][] expected = {{3.0d, 3.0d}, {3.0d, 3.0d}, {3.0d, 3.0d}, {2.5d, 2.0d}};
|
||||
double[][] expected =
|
||||
{{3.0d, 3.0d}, {3.0d, 3.0d}, {2.0d, 2.0d}, {3.0d, 3.0d}, {2.5d, 2.0d}};
|
||||
Controller[] withHoles = {Controller.ILI9341_2_8, Controller.ILI9341_2_4,
|
||||
Controller.ST7735_1_8, Controller.ST7789_1_54};
|
||||
Controller.ST7735_0_96, Controller.ST7735_1_8, Controller.ST7789_1_54};
|
||||
for (int i = 0; i < withHoles.length; i++) {
|
||||
Assert.assertEquals(withHoles[i] + " inset", expected[i][0], withHoles[i].getHoleInsetMm(),
|
||||
0.01d);
|
||||
@@ -503,21 +571,33 @@ public class TFTDisplayTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Every board in the family carries its main header on the bottom edge and stands above it, so
|
||||
* switching the variant property never turns a drawing end for end. The boards disagreed on this
|
||||
* for a while -- the round one and the 2.4" at the bottom, the other three at the top -- and a
|
||||
* user who changed variants found every wire landing on the wrong end of the part.
|
||||
* Which edge the main row leaves by, which is a fact about each board rather than a drawing
|
||||
* convention: with the pin order fixed left to right, a header at the top and a header at the
|
||||
* bottom are mirror images and not the same part. The two ILI9341s and the round board stand
|
||||
* above their row; the
|
||||
* 0.96", the 1.8" and the 1.54" hang below theirs. Asserted for every variant because nothing
|
||||
* else in the geometry would notice one being flipped.
|
||||
*/
|
||||
@Test
|
||||
public void everyBoardStandsAboveItsHeader() {
|
||||
public void headerSitsOnItsOwnEdge() {
|
||||
for (Controller controller : Controller.values()) {
|
||||
TFTDisplay display = of(controller);
|
||||
Rectangle2D bounds = display.getBodyShape().getBounds2D();
|
||||
Point2D firstPin = display.getControlPoint(0);
|
||||
double fromEdge = controller.isHeaderAtTop() ? firstPin.getY() - bounds.getY()
|
||||
: bounds.getMaxY() - firstPin.getY();
|
||||
|
||||
Assert.assertEquals(controller + " header offset", controller.getHeaderOffsetMm(),
|
||||
mm(bounds.getMaxY() - firstPin.getY()), 0.01d);
|
||||
mm(fromEdge), 0.01d);
|
||||
}
|
||||
// A roll call rather than a rule: every one of these was sourced from the part, and a future
|
||||
// variant must be too rather than inheriting whichever way the majority happens to go.
|
||||
Assert.assertTrue("0.96\"", Controller.ST7735_0_96.isHeaderAtTop());
|
||||
Assert.assertTrue("1.8\"", Controller.ST7735_1_8.isHeaderAtTop());
|
||||
Assert.assertTrue("1.54\"", Controller.ST7789_1_54.isHeaderAtTop());
|
||||
Assert.assertFalse("2.8\"", Controller.ILI9341_2_8.isHeaderAtTop());
|
||||
Assert.assertFalse("2.4\"", Controller.ILI9341_2_4.isHeaderAtTop());
|
||||
Assert.assertFalse("1.28\" round", Controller.GC9A01_1_28.isHeaderAtTop());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1752,6 +1752,83 @@ reach the hole columns, while the 2.8"'s same row on a 50 mm board passes inside
|
||||
SD row does on both. `mountingHolesDoNotOverlapAnyPin` asserts that over every variant and every
|
||||
pin, and it is what now justifies the 6.92 mm rather than a figure anyone has to take on trust.
|
||||
|
||||
### 12.2.3 The 0.96" ST7735, and what it proved about §12.2.2
|
||||
|
||||
The first board added after the standardisation broke it. The 0.96" 80x160 ST7735 carries its
|
||||
eight-pin row on the **top** edge — the maintainer gave the figure and a photograph of the part —
|
||||
so it hangs below its row where the other five stand above theirs.
|
||||
|
||||
**That is not a drawing preference, which is what §12.2.2 assumed it was.** Control point zero is
|
||||
always the leftmost pin, so with the pin order fixed, a header at the top and a header at the bottom
|
||||
are mirror images of each other rather than the same board turned round. Which edge the row leaves
|
||||
by is therefore a fact about the part, in the same class as its outline, and it has to be sourced
|
||||
per board rather than settled once for the family.
|
||||
|
||||
§12.2.2's *reason* still stands — switching variants should not flip a drawing — but it loses to
|
||||
drawing the part correctly, which is the stronger obligation. `isHeaderAtTop()` is back, carried per
|
||||
variant; `getBoardY`, the second row's sign and the silk's side all branch on it again. What does
|
||||
**not** come back is the thing that made the old flag wrong: it no longer stands in for `isRound()`,
|
||||
and the deeper header-side hole inset now follows the header's edge rather than assuming the bottom.
|
||||
|
||||
**Two boards were flipped by rule, and the rule had them backwards.** The 1.8" ST7735 and the
|
||||
1.54" ST7789 went bottom-headed in §12.2.2 on the premise that the family shared one orientation,
|
||||
with neither orientation sourced. Checked against the parts, **both are headed at the top**, so
|
||||
§12.2.2 had moved them away from the truth rather than towards it — the standardisation's one
|
||||
lasting effect on those two was to make them wrong, and it went unnoticed for exactly as long as no
|
||||
one looked.
|
||||
|
||||
The family divides three and three: the 0.96", the 1.8" and the 1.54" are headed at the top, the
|
||||
two ILI9341s and the round board at the bottom. That there is no majority to fall back on is useful
|
||||
rather than awkward, because it removes the temptation to have a default: **the short constructor
|
||||
now takes the orientation too, so every constant states its own edge and none inherits one.** A
|
||||
future variant has to answer the question rather than be answered for.
|
||||
|
||||
`headerSitsOnItsOwnEdge` ends with a roll call of all six rather than a rule, which is the only
|
||||
form an assertion about sourced facts can honestly take.
|
||||
|
||||
Checking the orientation also corrected the 1.8"'s pin names, which had read `SCK` and `BL` where
|
||||
the board prints `SCL` and `BLK`. That left its array identical to the 1.54"'s and to the 0.96"'s,
|
||||
so all three now share `PIN_NAMES_SPI_8PIN`, asserted by identity: two different controllers behind
|
||||
one header, because what a module of this size brings out is the four-wire SPI interface plus reset,
|
||||
data/command and the backlight, and that does not vary with the driver. Two near-duplicate pin-name
|
||||
tests collapsed into one.
|
||||
|
||||
### 12.2.4 Two structural additions the 0.96" needed
|
||||
|
||||
**A panel that does not span the board.** Every other variant's glass runs the full width, so the
|
||||
drawing filled it edge to edge and centred the lit area on the board. This one's bezel is 23.7 mm on
|
||||
a 30 mm board, pushed 2 mm from the right edge and leaving 4.3 mm on the left — the bare L is where
|
||||
the driver circuitry sits. The offset is **across the board only**; down it the bezel is centred in
|
||||
the PCB, which on a board whose four holes share one inset is the family's existing rule and needs
|
||||
no figure at all.
|
||||
|
||||
That was not obvious from the figures as first supplied, which placed the bezel 5.15 mm from the top
|
||||
edge against 5.6 mm for centring. A 0.45 mm discrepancy was carried as a measurement and given a
|
||||
`topMm` on the new `Glass`, and the maintainer then corrected it: the bezel is centred. The field
|
||||
came out again, having had exactly one user and never a real one. Worth recording as a near miss of
|
||||
the kind §12.2.1's bezel paragraph caught and this one did not — a supplied figure that sits within
|
||||
half a millimetre of what the existing rule already produces is more likely a reading than a
|
||||
departure, and is worth querying before it earns a mechanism.
|
||||
|
||||
Two per-variant figures remain, which is still enough to want an object on a constructor that
|
||||
already took fifteen arguments: the glass figures live in a nested `Glass` carrying length, width
|
||||
and right inset — the same treatment `OLEDDisplay.Version` gives its `Layout`. A board with no
|
||||
separate dark panel passes `null` instead of a zero length, which says the same thing more plainly.
|
||||
|
||||
**A lit area that belongs to the panel rather than to the board.** The screen is now centred in the
|
||||
glass horizontally rather than on the board. For the five variants whose glass spans the board those
|
||||
are the same point, so nothing moved; for the 0.96" they differ by 1.15 mm. The vertical rule is
|
||||
unchanged — centred in the glass unless the variant measured it, which only the 2.4" does.
|
||||
|
||||
The 0.96"'s own lit area is measured (21.7 x 10.8 mm) rather than derived from the diagonal, and it
|
||||
is landscape where every other board's is portrait. It sits centred in the bezel with a 1 mm frame on
|
||||
all four sides, which `theOffsetPanelIsPlacedFromTheRightEdge` asserts along with the horizontal
|
||||
offset being a figure that centring would have got wrong.
|
||||
|
||||
One name was settled by the maintainer rather than copied through: the pin list supplied read `DS`
|
||||
in seventh place, which is `CS` on the ST7735 and on the 1.8" board already in the enum. Node names
|
||||
reach the netlist, so it was asked rather than assumed.
|
||||
|
||||
**The 2.8" wanted the same row, and it was a missing header rather than an enhancement.** Its
|
||||
label has always read "(Touch + SD)" while its 14 pins are display and touch only, so the socket's
|
||||
four lines had nowhere to go. It now carries `PIN_NAMES_SD` on the same terms as the 2.4": same
|
||||
@@ -1795,7 +1872,8 @@ rediscover that.
|
||||
Ranked by how often the part appears in builds, not by how cheap it is to draw.
|
||||
|
||||
1. ~~**2.4" ILI9341**~~ — **done**, §12.2 and §12.2.1.
|
||||
2. **0.96" 80x160 ST7735**, and the 1.14" and 2.0" ST7789 that the Pico ecosystem buys.
|
||||
2. ~~**0.96" 80x160 ST7735**~~ — **done**, §12.2.3 and §12.2.4. Still open in this family: the
|
||||
1.3" 240x240 ST7789, and the 1.14" and 2.0" ST7789 that the Pico ecosystem buys.
|
||||
3. **MAX7219 8-digit 7-segment module** — the clock and counter staple, and this package already
|
||||
models a MAX7219 cascade for the matrix. Needs §12.3's `digitCount` work first.
|
||||
4. **6-digit TM1637 and 2-digit 0.56"** — the rest of the clock and thermometer range.
|
||||
|
||||
Reference in New Issue
Block a user