oled tweak

This commit is contained in:
Bane Stojkovic
2026-10-08 21:00:00 +02:00
parent 02615f5150
commit a4fbd35c77
3 changed files with 321 additions and 66 deletions
@@ -4958,6 +4958,15 @@
<defaultFlag>false</defaultFlag>
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
</org.diylc.core.Template>
<org.diylc.core.Template>
<name>0.96" SSD1306 SPI (8-Pin)</name>
<values>
<entry><string>Size</string><diylc.displays.OLEDDisplay_-Version>SSD1306_0_96</diylc.displays.OLEDDisplay_-Version></entry>
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_8Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
</values>
<defaultFlag>false</defaultFlag>
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
</org.diylc.core.Template>
<org.diylc.core.Template>
<name>0.91" SSD1306 I2C</name>
<values>
@@ -4985,6 +4994,15 @@
<defaultFlag>false</defaultFlag>
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
</org.diylc.core.Template>
<org.diylc.core.Template>
<name>0.91" SSD1306 SPI (8-Pin)</name>
<values>
<entry><string>Size</string><diylc.displays.OLEDDisplay_-Version>SSD1306_0_91</diylc.displays.OLEDDisplay_-Version></entry>
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_8Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
</values>
<defaultFlag>false</defaultFlag>
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
</org.diylc.core.Template>
<org.diylc.core.Template>
<name>1.3" SH1106 I2C</name>
<values>
@@ -5012,6 +5030,15 @@
<defaultFlag>false</defaultFlag>
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
</org.diylc.core.Template>
<org.diylc.core.Template>
<name>1.3" SH1106 SPI (8-Pin)</name>
<values>
<entry><string>Size</string><diylc.displays.OLEDDisplay_-Version>SH1106_1_3</diylc.displays.OLEDDisplay_-Version></entry>
<entry><string>Interface</string><diylc.displays.OLEDDisplay_-OLEDInterface>SPI_8Pin</diylc.displays.OLEDDisplay_-OLEDInterface></entry>
</values>
<defaultFlag>false</defaultFlag>
<createdOn>2026-10-11T00:00:00.0000000</createdOn>
</org.diylc.core.Template>
</list>
</entry>
<entry>
@@ -24,6 +24,7 @@ package org.diylc.components.displays;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
@@ -67,6 +68,12 @@ public class OLEDDisplay extends AbstractMakerBoard {
public static Color ACTIVE_AREA_COLOR = Color.decode("#060D15");
public static Color PIXEL_BLUE = Color.decode("#00D4FF");
// Clearance a name keeps from the edge of its pad. The shared drawFlatRowPinLabels sits its
// labels 2.04 mm off the pin centre, which the 3.5 mm strip the Nokia and the TFT print into can
// afford; the 0.96" has 2.27 mm between header and glass and the 0.91" SPI board 2.5 mm, where
// that offset drops the text onto the display.
public static Size LABEL_GAP = new Size(0.5d, SizeUnit.mm);
public static final String[] PIN_NAMES_I2C = new String[] {"GND", "VCC", "SCL", "SDA"};
/**
@@ -89,6 +96,17 @@ public class OLEDDisplay extends AbstractMakerBoard {
public static final String[] PIN_NAMES_SPI_NO_CS =
new String[] {"GND", "VCC", "SCL", "SDA", "RES", "DC"};
/**
* The eight-pin board, which is the only one of these to bring the controller's logic rail out
* beside the module's input: VIN takes 4.8 - 5.2 V and VDD the 2.8 - 3.3 V the core runs on.
* They are separate rails and must not be tied together, so the extra pad is a different board
* rather than a different silkscreen -- as is the order, which follows the controller's own pin
* numbering and so starts at the data line and leaves the supplies to the far end, the reverse
* of where the other boards print them.
*/
public static final String[] PIN_NAMES_SPI_SPLIT_SUPPLY =
new String[] {"SDA", "SCL", "DC", "RES", "CS", "VDD", "VIN", "GND"};
private Version version = Version.SSD1306_0_96;
private OLEDInterface oledInterface = OLEDInterface.I2C_4Pin;
private Display screen = Display.VALUE;
@@ -161,9 +179,9 @@ public class OLEDDisplay extends AbstractMakerBoard {
/**
* The board the two properties name between them. On the 0.96" and the 1.3" the interface only
* changes how many pins the header carries, but the 0.91" is sold as two different boards -- a
* 38 x 12 mm one with a four-pin column on its left edge and a 38 x 20 mm one with a seven-pin
* row on top -- so the outline, the header edge, the hole pattern and the panel's position all
* come from the pair rather than from the size alone.
* 35.8 x 12 mm one with a four-pin column on its left edge and a 32.5 x 20.5 mm one with a
* seven-pin row on top -- so the outline, the header edge, the hole pattern and the panel's
* position all come from the pair rather than from the size alone.
*/
private Layout getLayout() {
return getVersion().getLayout(getOledInterface());
@@ -238,6 +256,66 @@ public class OLEDDisplay extends AbstractMakerBoard {
px(layout.getBoardWidthMm()), px(layout.getBoardLengthMm()), 8, 8);
}
/**
* The names the module prints in the strip between its header and its glass. They lie flat and
* read the way the board does, as the Nokia's and the TFT's do, rather than standing on end
* across the row the way the Arduino boards print theirs -- no strip here is deep enough for
* that. The 0.91" I2C board is the one whose header is a column, so its names lie to the right
* of the pins instead of below them, and a name that would reach the glass is left off.
*/
private void drawPinNames(Graphics2D g2d, double boardX, double boardY) {
Layout layout = getLayout();
double[][] offsets = getRelativeOffsets();
Point2D p0 = controlPoints[0];
double gap = LABEL_GAP.convertToPixels();
// What a label has to clear is the header body drawPinHeader fills, a square reaching one
// pixel further than PIN_SIZE either side of the pin rather than the pin itself; measured off
// the pin alone the names would be printed onto the pads.
double padHalf = Math.round(PIN_SIZE.convertToPixels()) + 1;
g2d.setFont(PIN_ROW_FLAT_FONT);
g2d.setColor(SILK_COLOR);
FontMetrics metrics = g2d.getFontMetrics();
// One reference glyph rather than each label's own box, so a row of them sits on one line.
// Every name on these boards is upper case and none carries a descender, so a box centred on
// the cap height lands each of them in the same place.
double capHeight = g2d.getFont().createGlyphVector(g2d.getFontRenderContext(), "X")
.getVisualBounds().getHeight();
boolean column = layout.isHeaderOnLeftEdge();
double glassEdge = column ? boardX + px(layout.getPanelLeftMm())
: boardY + px(layout.getPanelTopMm());
for (int i = 0; i < offsets.length; i++) {
String label = getSilkPinLabel(i);
if (label == null || label.isEmpty()) {
continue;
}
double pinX = p0.getX() + offsets[i][0];
double pinY = p0.getY() + offsets[i][1];
double room = glassEdge - (column ? pinX : pinY) - padHalf;
double extent = column ? metrics.stringWidth(label) : capHeight;
if (extent > room) {
continue;
}
// A gap off the pad, or the middle of the strip where the strip cannot afford the whole gap:
// the 0.96" leaves 1.13 mm between its pads and its glass to print 0.73 mm of lettering in,
// and splitting what is left over is the only placement that clears both.
double offset = padHalf + Math.min(gap, (room - extent) / 2.0);
if (column) {
StringUtils.drawCenteredText(g2d, label, pinX + offset, pinY, HorizontalAlignment.LEFT,
VerticalAlignment.CENTER);
} else {
StringUtils.drawCenteredText(g2d, label, pinX, pinY + offset + capHeight / 2.0,
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
}
}
}
@Override
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
IDrawingObserver drawingObserver) {
@@ -299,18 +377,21 @@ public class OLEDDisplay extends AbstractMakerBoard {
g2d.draw(new RoundRectangle2D.Double(glassX, glassY, glassW, glassH, 4, 4));
// The lit area is offset within the panel horizontally -- the driver's bonding region takes
// one end of the glass -- and centred across it. Both are figures of the panel part rather
// than of the board it is mounted on, so the two 0.91" boards share them.
// one end of the glass -- and sits high in it vertically, the bonding region running along
// the bottom on every one of these panels. Following the TFT boards, its top is read from
// the board's top edge, which is the edge it is measured from on a module in hand.
double activeW = px(version.getActiveWidthMm());
double activeH = px(version.getActiveLengthMm());
double activeX = glassX + px(version.getActiveLeftInPanelMm());
double activeY = glassY + (glassH - activeH) / 2.0;
double activeY = boardY + px(layout.getActiveTopMm());
g2d.setColor(ACTIVE_AREA_COLOR);
g2d.fill(new RoundRectangle2D.Double(activeX, activeY, activeW, activeH, 2, 2));
MakerBoardPainter.drawScreenText(g2d,
new Rectangle2D.Double(activeX, activeY, activeW, activeH), getPixelColor(), getScreen(),
getName(), getValueForDisplay());
drawPinNames(g2d, boardX, boardY);
}
g2d.setTransform(oldTx);
@@ -343,14 +424,18 @@ public class OLEDDisplay extends AbstractMakerBoard {
}
public enum OLEDInterface {
// Labels stay short because they reach the BOM's value column through getVariantLabel and are
// printed on the lit area, which the 0.91" has little of: "SPI, 7-pin" and "SPI, 6-pin" were
// tried and the glass stopped printing anything at all. The pin array belongs here rather than
// on the board, because all three sizes bring the same lines out in the same order and only the
// interface decides which set that is.
// Every SPI label states its pin count, which is the only thing that separates the three of
// them; a bare "SPI" beside "SPI-6" would read as an omission on a BOM row and the reader
// would have to know which board it meant. Labels stay short because they reach the BOM's
// value column through getVariantLabel and are printed on the lit area, which the 0.91" has
// little of: "SPI, 7-pin" and "SPI, 6-pin" were tried and the glass stopped printing anything
// at all. The pin array belongs here rather than
// on the board, because a row is the same at all three sizes and only the interface decides
// which row that is.
I2C_4Pin("I2C", PIN_NAMES_I2C),
SPI_7Pin("SPI", PIN_NAMES_SPI),
SPI_6Pin("SPI-6", PIN_NAMES_SPI_NO_CS);
SPI_6Pin("SPI-6", PIN_NAMES_SPI_NO_CS),
SPI_7Pin("SPI-7", PIN_NAMES_SPI),
SPI_8Pin("SPI-8", PIN_NAMES_SPI_SPLIT_SUPPLY);
private final String label;
private final String[] pinNames;
@@ -365,22 +450,25 @@ public class OLEDDisplay extends AbstractMakerBoard {
}
public enum Version {
// The panel and its lit area belong to the display part, so they are carried here and shared
// by both of a size's boards; everything that belongs to a PCB is on the Layout beside them.
// Lit areas are the nominal pixel count at the panel's dot pitch -- 128 x 64 at 0.17 mm,
// 128 x 32 at 0.175 mm, 128 x 64 at 0.23 mm -- which makes them derivations rather than
// The panel and its lit area belong to the display part, so their sizes are carried here and
// shared by both of a size's boards; everything that belongs to a PCB is on the Layout beside
// them, and so is where the lit area sits down the board, which the two 0.91" boards disagree
// on.
// The 0.96"'s and the 1.3"'s panels are measured off a module, and so is the 0.96"'s lit
// area; the other lit areas are the nominal pixel count at the panel's dot pitch -- 128 x 32
// at 0.175 mm and 128 x 64 at 0.23 mm -- which makes those derivations rather than
// measurements, noted so they are not built upon.
//
// The lit area's offset inside the panel is measured on the 0.91" and happens to be the
// centred position on the other two; oneCentredPanel in the test pins that down, so a later
// correction to a panel or a board cannot leave a stale offset behind.
SSD1306_0_96("0.96\" SSD1306 128x64", 26.7d, 19.3d, 21.744d, 10.864d, 2.478d,
new Layout(27.0d, 27.0d, 1.8d, false, 1.5d, 2.0d, 0.15d, 3.85d)),
// centred position on the other two; centredPanelsStoreTheCentredOffsets in the test pins
// that down, so a later correction to a panel or a board cannot leave a stale offset behind.
SSD1306_0_91("0.91\" SSD1306 128x32", 30.0d, 12.0d, 22.384d, 5.584d, 2.1d,
new Layout(38.0d, 12.0d, 1.5d, true, 0d, 0d, 5.0d, 0d),
new Layout(38.0d, 20.0d, 1.5d, false, 2.5d, 2.0d, 1.25d, 4.0d)),
SH1106_1_3("1.3\" SH1106 128x64", 31.4d, 16.7d, 29.42d, 14.7d, 0.99d,
new Layout(35.4d, 33.5d, 1.5d, false, 2.0d, 3.0d, 2.0d, 8.4d));
new Layout(35.8d, 12.0d, 1.5d, true, 0d, 0d, 5.0d, 0d, 2.35d),
new Layout(32.5d, 20.5d, 1.5d, false, 2.5d, 2.0d, 1.25d, 4.0d, 6.6d)),
SSD1306_0_96("0.96\" SSD1306 128x64", 26.7d, 19.26d, 21.74d, 11.0d, 2.48d,
new Layout(27.0d, 27.0d, 1.6d, false, 2.0d, 2.0d, 0.15d, 3.87d, 6.14d)),
SH1106_1_3("1.3\" SH1106 128x64", 35.4d, 23.0d, 29.42d, 14.7d, 2.99d,
new Layout(35.4d, 33.5d, 1.5d, false, 2.5d, 3.0d, 0d, 5.25d, 7.35d));
private final String label;
private final double panelWidthMm;
@@ -418,10 +506,11 @@ public class OLEDDisplay extends AbstractMakerBoard {
public double getActiveLeftInPanelMm() { return activeLeftInPanelMm; }
/**
* The two SPI headers share a board. That is a derivation rather than a measurement: a 6-pin
* module is a PCB of its own with one fewer pad, and only its outline would say whether it
* matches the 7-pin board it is modelled on. The header is centred on the width either way, so
* the shorter row sits correctly on the board it borrows.
* The three SPI headers share a board. That is a derivation rather than a measurement: the
* 6-pin and 8-pin modules are PCBs of their own, one pad short and one pad long, and only
* their outlines would say whether either matches the 7-pin board they are modelled on. The
* header is centred on the width in every case, so a shorter or longer row still sits
* correctly on the board it borrows.
*/
public Layout getLayout(OLEDInterface oledInterface) {
return oledInterface == OLEDInterface.I2C_4Pin ? i2cLayout : spiLayout;
@@ -434,9 +523,7 @@ public class OLEDDisplay extends AbstractMakerBoard {
* different boards on the 0.91".
*
* <p>Hole positions are an inset from each of the two edges nearest a hole, following the TFT
* boards. The 0.96"'s pattern was given as a 24 mm centre-to-centre spacing instead, which on
* its 27 mm board is this inset; {@code holePatternMatchesTheSpecifiedSpacing} keeps the two
* readings in step.
* boards.
*/
public static class Layout {
@@ -448,10 +535,11 @@ public class OLEDDisplay extends AbstractMakerBoard {
private final double holeSizeMm;
private final double panelLeftMm;
private final double panelTopMm;
private final double activeTopMm;
Layout(double boardWidthMm, double boardLengthMm, double headerOffsetMm,
boolean headerOnLeftEdge, double holeInsetMm, double holeSizeMm, double panelLeftMm,
double panelTopMm) {
double panelTopMm, double activeTopMm) {
this.boardWidthMm = boardWidthMm;
this.boardLengthMm = boardLengthMm;
this.headerOffsetMm = headerOffsetMm;
@@ -460,6 +548,7 @@ public class OLEDDisplay extends AbstractMakerBoard {
this.holeSizeMm = holeSizeMm;
this.panelLeftMm = panelLeftMm;
this.panelTopMm = panelTopMm;
this.activeTopMm = activeTopMm;
}
public double getBoardWidthMm() { return boardWidthMm; }
@@ -469,6 +558,12 @@ public class OLEDDisplay extends AbstractMakerBoard {
public double getPanelLeftMm() { return panelLeftMm; }
public double getPanelTopMm() { return panelTopMm; }
/**
* Top of the lit area measured from the board's top edge, following the TFT boards, which is
* the edge it is measured from on a module in hand.
*/
public double getActiveTopMm() { return activeTopMm; }
/** Clearance from the edge the header sits on, whichever edge that is. */
public double getHeaderOffsetMm() { return headerOffsetMm; }
@@ -24,6 +24,9 @@ package org.diylc.components.displays;
import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.common.Display;
@@ -92,10 +95,10 @@ public class OLEDDisplayTest {
Layout i2c = Version.SSD1306_0_91.getLayout(OLEDInterface.I2C_4Pin);
Layout spi = Version.SSD1306_0_91.getLayout(OLEDInterface.SPI_7Pin);
Assert.assertEquals("I2C width", 35.8d, i2c.getBoardWidthMm(), 0.01d);
Assert.assertEquals("I2C length", 12.0d, i2c.getBoardLengthMm(), 0.01d);
Assert.assertEquals("SPI length", 20.0d, spi.getBoardLengthMm(), 0.01d);
Assert.assertEquals("the two share a long edge", i2c.getBoardWidthMm(),
spi.getBoardWidthMm(), 0.01d);
Assert.assertEquals("SPI width", 32.5d, spi.getBoardWidthMm(), 0.01d);
Assert.assertEquals("SPI length", 20.5d, spi.getBoardLengthMm(), 0.01d);
Assert.assertTrue("the I2C column should stand on the left edge", i2c.isHeaderOnLeftEdge());
Assert.assertFalse("the SPI row should lie along the top edge", spi.isHeaderOnLeftEdge());
Assert.assertFalse("the I2C board has no mounting holes", i2c.hasMountingHoles());
@@ -127,6 +130,16 @@ public class OLEDDisplayTest {
}
}
@Test
public void controlPointsAreTheEightPinRowInOrder() {
OLEDDisplay display = of(Version.SSD1306_0_96, OLEDInterface.SPI_8Pin);
String[] expected = new String[] {"SDA", "SCL", "DC", "RES", "CS", "VDD", "VIN", "GND"};
Assert.assertEquals(expected.length, display.getControlPointCount());
for (int i = 0; i < expected.length; i++) {
Assert.assertEquals("pin " + i, expected[i], display.getControlPointNodeName(i));
}
}
/** Every size is sold in every interface, so no size may carry a shorter pin array. */
@Test
public void everyInterfaceExistsAtEverySize() {
@@ -137,6 +150,8 @@ public class OLEDDisplayTest {
of(version, OLEDInterface.SPI_7Pin).getControlPointCount());
Assert.assertEquals(version + " SPI-6", 6,
of(version, OLEDInterface.SPI_6Pin).getControlPointCount());
Assert.assertEquals(version + " SPI-8", 8,
of(version, OLEDInterface.SPI_8Pin).getControlPointCount());
}
}
@@ -164,6 +179,32 @@ public class OLEDDisplayTest {
}
}
/**
* The eight-pin board carries the same seven lines as the seven-pin one plus a second supply,
* so what has to hold is that the set has grown by exactly VDD and nothing has been dropped --
* the order cannot be compared, because this row follows the controller's pin numbering and runs
* the other way. VDD and VIN are separate rails, so neither may be renamed to the other or to
* the VCC the shorter rows print, which a set comparison would let through if both were spelled
* the same.
*/
@Test
public void theEightPinBoardAddsOnlyTheLogicSupply() {
Set<String> seven = new HashSet<String>(Arrays.asList(OLEDInterface.SPI_7Pin.getPinNames()));
Set<String> eight = new HashSet<String>(Arrays.asList(OLEDInterface.SPI_8Pin.getPinNames()));
Assert.assertEquals("no pin names repeat in the row", 8,
OLEDInterface.SPI_8Pin.getPinNames().length);
Assert.assertEquals("no pin names repeat in the row", 8, eight.size());
Assert.assertTrue("VIN replaces the shorter rows' VCC", eight.contains("VIN"));
Assert.assertTrue("the logic rail is the pin that is added", eight.contains("VDD"));
Assert.assertFalse("VCC belongs to the rows with one supply", eight.contains("VCC"));
seven.remove("VCC");
eight.remove("VIN");
eight.remove("VDD");
Assert.assertEquals("the signal lines are the seven-pin row's", seven, eight);
}
/** Switching either property must resize the array, not leave the longer one's points behind. */
@Test
public void switchingInterfaceResizesTheControlPoints() {
@@ -175,6 +216,8 @@ public class OLEDDisplayTest {
Assert.assertEquals(7, display.getControlPointCount());
display.setOledInterface(OLEDInterface.SPI_6Pin);
Assert.assertEquals(6, display.getControlPointCount());
display.setOledInterface(OLEDInterface.SPI_8Pin);
Assert.assertEquals(8, display.getControlPointCount());
display.setOledInterface(OLEDInterface.I2C_4Pin);
Assert.assertEquals(4, display.getControlPointCount());
}
@@ -244,13 +287,15 @@ public class OLEDDisplayTest {
}
/**
* Each lit area is its pixel count at the panel's dot pitch, so its aspect is the pixel aspect:
* a 2:1 letterbox on the two 128 x 64 panels and 4:1 on the 128 x 32. This is the figure the
* 0.96" was drawn wrong against for as long as its panel was near-square.
* A lit area's aspect is very nearly the pixel aspect: a 2:1 letterbox on the two 128 x 64
* panels and 4:1 on the 128 x 32. This is the figure the 0.96" was drawn wrong against for as
* long as its panel was near-square. The two derived panels sit exactly on it because that is
* how they were arrived at; the measured 0.96" comes out a little under, and the tolerance here
* is wide enough to allow that and nothing like a transposition.
*/
@Test
public void litAreaMatchesThePixelAspect() {
Assert.assertEquals(2.0d, aspect(Version.SSD1306_0_96), 0.01d);
Assert.assertEquals(2.0d, aspect(Version.SSD1306_0_96), 0.05d);
Assert.assertEquals(4.0d, aspect(Version.SSD1306_0_91), 0.01d);
Assert.assertEquals(2.0d, aspect(Version.SH1106_1_3), 0.01d);
}
@@ -269,6 +314,16 @@ public class OLEDDisplayTest {
version.getActiveLengthMm() <= version.getPanelLengthMm());
Assert.assertTrue(version + " lit area starts off the panel",
version.getActiveLeftInPanelMm() >= 0);
for (OLEDInterface oledInterface : OLEDInterface.values()) {
Layout layout = version.getLayout(oledInterface);
String board = version + " " + oledInterface;
Assert.assertTrue(board + " lit area starts above its panel",
layout.getActiveTopMm() >= layout.getPanelTopMm());
Assert.assertTrue(board + " lit area runs off the bottom of its panel",
layout.getActiveTopMm() + version.getActiveLengthMm() <= layout.getPanelTopMm()
+ version.getPanelLengthMm());
}
}
}
@@ -340,24 +395,50 @@ public class OLEDDisplayTest {
}
/**
* The 0.91" SPI board centres its lit area on the board's height, which is how it was measured,
* rather than hanging the panel below the header the way the taller boards do.
* The 0.91" SPI board's panel offset of 1.25 mm is exactly centred on its 32.5 mm width -- two
* independent readings agreeing, which is what makes this worth asserting. The vertical pair do
* not agree: the panel hangs below the header rather than sitting centred on the height, so
* there is nothing to cross-check down the board.
*/
@Test
public void theSmallSpiBoardCentresItsLitArea() {
public void theSmallSpiBoardCentresItsPanelAcrossTheBoard() {
Version version = Version.SSD1306_0_91;
Layout layout = version.getLayout(OLEDInterface.SPI_7Pin);
double litTop = layout.getPanelTopMm()
+ (version.getPanelLengthMm() - version.getActiveLengthMm()) / 2.0;
Assert.assertEquals(layout.getBoardLengthMm() / 2.0,
litTop + version.getActiveLengthMm() / 2.0, 0.01d);
Assert.assertEquals("the panel is off centre across the board",
(layout.getBoardWidthMm() - version.getPanelWidthMm()) / 2.0, layout.getPanelLeftMm(),
0.01d);
}
/**
* Every lit area is measured down from its board's top edge rather than centred on the panel:
* the driver's bonding region takes the bottom of the glass on all of these, so the lit area
* sits high in the frame and centring it would drop it -- by 1.86 mm on the 0.96", 0.86 mm on
* the 0.91" I2C board, 0.61 mm on its SPI sibling and 2.05 mm on the 1.3". The centred position
* is asserted beside each measurement because a stale one would still land inside the panel and
* clear every pin and hole.
*/
@Test
public void measuredLitAreasSitHighInTheirPanels() {
assertLitAreaTop(Version.SSD1306_0_96, OLEDInterface.I2C_4Pin, 6.14d, 8.0d);
assertLitAreaTop(Version.SSD1306_0_91, OLEDInterface.I2C_4Pin, 2.35d, 3.208d);
assertLitAreaTop(Version.SSD1306_0_91, OLEDInterface.SPI_7Pin, 6.6d, 7.208d);
assertLitAreaTop(Version.SH1106_1_3, OLEDInterface.I2C_4Pin, 7.35d, 9.4d);
}
private static void assertLitAreaTop(Version version, OLEDInterface oledInterface,
double measuredMm, double centredMm) {
Layout layout = version.getLayout(oledInterface);
Assert.assertEquals(version + " measured from the top edge", measuredMm,
layout.getActiveTopMm(), 0.01d);
Assert.assertEquals(version + " centring would drop it", centredMm, layout.getPanelTopMm()
+ (version.getPanelLengthMm() - version.getActiveLengthMm()) / 2.0, 0.01d);
}
/**
* The panel is wide enough on every one of these boards to share the holes' horizontal span, so
* the vertical axis is the only one that can keep them apart -- a symmetric check would fail on
* a board that is drawn correctly. The 0.91" SPI board is the tight one, clearing each hole row
* by 0.5 mm.
* a board that is drawn correctly. The 0.91" SPI board is the tight one, clearing its
* header-side holes by 0.5 mm.
*/
@Test
public void panelClearsTheMountingHolesVertically() {
@@ -381,16 +462,16 @@ public class OLEDDisplayTest {
}
/**
* The strip between a top-edge row and the panel below it. On the 0.96" it is 2.05 mm, which is
* the whole reason that board carries no silkscreen: a flat label needs about 2.7 mm and would
* cross the panel's top edge. The 1.3" has 6.9 mm and could print its names, which is noted in
* the plan rather than done here.
* The strip between a top-edge row and the panel below it, which is what the pin names have to
* fit into. The 0.96"'s 2.27 mm is the shallowest of them and is why these boards place their
* own labels a fixed gap off the pad: the shared helper's 2.04 mm offset is pitched for the
* 3.5 mm the Nokia and the TFT have and would print the names onto the glass.
*/
@Test
public void panelClearsThePinRow() {
Assert.assertEquals("0.96\"", 2.05d, stripDepthMm(Version.SSD1306_0_96), 0.01d);
Assert.assertEquals("0.96\"", 2.27d, stripDepthMm(Version.SSD1306_0_96), 0.01d);
Assert.assertEquals("0.91\" SPI", 2.5d, stripDepthMm(Version.SSD1306_0_91), 0.01d);
Assert.assertEquals("1.3\"", 6.9d, stripDepthMm(Version.SH1106_1_3), 0.01d);
Assert.assertEquals("1.3\"", 3.75d, stripDepthMm(Version.SH1106_1_3), 0.01d);
}
private static double stripDepthMm(Version version) {
@@ -435,17 +516,69 @@ public class OLEDDisplayTest {
}
/**
* The 0.96"'s hole pattern was specified as a 24 mm centre-to-centre spacing in both directions
* and is stored as the equivalent inset, following the TFT boards. If its outline is ever
* corrected the inset has to be recomputed, and this is what says so.
* Every board prints its pin names, and none of them prints one on the glass. The strip they go
* in is 2.27 mm on the 0.96" against the 3.5 mm the sibling displays print into, so this is the
* check that the tighter placement actually clears the panel -- no geometry assertion here would
* notice a label drawn over the display, and on the 0.96" the shared helper's offset would put
* one there.
*
* <p>The silkscreen colour is repointed at a colour nothing else on these boards draws in, which
* is what makes the count unambiguous: mounting holes are filled in the canvas's white and would
* otherwise be indistinguishable from lettering. Appearance constants are deliberately not
* final so that they can be overridden at runtime, which is what this leans on.
*/
@Test
public void holePatternMatchesTheSpecifiedSpacing() {
Layout layout = Version.SSD1306_0_96.getLayout(OLEDInterface.I2C_4Pin);
Assert.assertEquals("across", 24.0d,
layout.getBoardWidthMm() - 2 * layout.getHoleInsetMm(), 0.01d);
Assert.assertEquals("down", 24.0d,
layout.getBoardLengthMm() - 2 * layout.getHoleInsetMm(), 0.01d);
public void pinNamesAreDrawnAndStayOffThePanel() {
Color silk = AbstractMakerBoard.SILK_COLOR;
AbstractMakerBoard.SILK_COLOR = Color.MAGENTA;
try {
for (Version version : Version.values()) {
for (OLEDInterface oledInterface : OLEDInterface.values()) {
Layout layout = version.getLayout(oledInterface);
OLEDDisplay display = of(version, oledInterface);
int[] pixels = MakerBoardTestSupport.renderPixels(display);
int side = (int) Math.round(Math.sqrt(pixels.length));
Rectangle2D outline = display.getBodyShape().getBounds2D();
Rectangle2D glass = new Rectangle2D.Double(
outline.getX() + mmToPx(layout.getPanelLeftMm()),
outline.getY() + mmToPx(layout.getPanelTopMm()),
mmToPx(version.getPanelWidthMm()), mmToPx(version.getPanelLengthMm()));
int printed = 0;
int onGlass = 0;
for (int i = 0; i < pixels.length; i++) {
if (!isSilk(pixels[i])) {
continue;
}
printed++;
if (glass.contains(i % side, i / side)) {
onGlass++;
}
}
String board = version + " " + oledInterface;
Assert.assertTrue(board + " prints no pin names at all", printed > 0);
Assert.assertEquals(board + " prints a pin name on the panel", 0, onGlass);
}
}
} finally {
AbstractMakerBoard.SILK_COLOR = silk;
}
}
/**
* Antialiasing thins the lettering where it meets whatever is behind it, so a label clipping the
* glass by a fraction of a millimetre arrives dimmed rather than at full strength. Matching the
* hue rather than the value catches those too: nothing else drawn on these boards -- the blue
* PCB, the cyan pads, the dark glass, the lit text, the gold and white of a mounting hole --
* leaves red and blue both well ahead of green.
*/
private static boolean isSilk(int argb) {
int red = (argb >> 16) & 0xFF;
int green = (argb >> 8) & 0xFF;
int blue = argb & 0xFF;
return red > 60 && blue > 60 && red > green * 1.5 && blue > green * 1.5;
}
/** Without this the BOM collapses six different modules into one or two rows. */
@@ -453,10 +586,10 @@ public class OLEDDisplayTest {
public void bomValueCarriesSizeAndInterface() {
Assert.assertEquals("0.96\" SSD1306 128x64, I2C",
of(Version.SSD1306_0_96, OLEDInterface.I2C_4Pin).getValueForDisplay());
Assert.assertEquals("1.3\" SH1106 128x64, SPI",
Assert.assertEquals("1.3\" SH1106 128x64, SPI-7",
of(Version.SH1106_1_3, OLEDInterface.SPI_7Pin).getValueForDisplay());
java.util.Set<String> values = new java.util.HashSet<String>();
Set<String> values = new HashSet<String>();
for (Version version : Version.values()) {
for (OLEDInterface oledInterface : OLEDInterface.values()) {
values.add(of(version, oledInterface).getValueForDisplay());