mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
nokia
This commit is contained in:
@@ -162,6 +162,10 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
// labels along a row run across the board rather than down a column of pins, so they can afford
|
||||
// a size the tightly stacked column labels cannot
|
||||
public static Font PIN_ROW_FONT = new Font(SILK_FONT_FAMILY, Font.PLAIN, 9);
|
||||
// a label lying along its row has only the pin pitch to fit into, so it runs a size down from
|
||||
// the rotated ones and sits closer to the pin, which is all the room a shallow strip has
|
||||
public static Font PIN_ROW_FLAT_FONT = new Font(SILK_FONT_FAMILY, Font.PLAIN, 8);
|
||||
public static Size PIN_ROW_FLAT_LABEL_OFFSET = new Size(2.04d, SizeUnit.mm);
|
||||
|
||||
protected Orientation orientation = Orientation.DEFAULT;
|
||||
protected Point2D[] controlPoints = new Point2D[] {new Point2D.Double(0, 0)};
|
||||
@@ -467,6 +471,44 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to draw pin names lying flat along a horizontal row of pins, reading left to right the
|
||||
* way the board itself does, for a board whose strip beside the header is shallower than the
|
||||
* longest name is long. {@link #drawRowPinLabels} stands its labels on end across the row, so
|
||||
* each one has the whole depth of the board to grow into; these lie in the row's own direction
|
||||
* and have only the tenth of an inch between two pins, so they run in a smaller font.
|
||||
*
|
||||
* @param g2d Graphics2D context (already transformed for board orientation)
|
||||
* @param x Unrotated first control point X coordinate (P0.getX())
|
||||
* @param y Unrotated first control point Y coordinate (P0.getY())
|
||||
* @param offsets Array of [x, y] relative offsets for all control points
|
||||
* @param startIndex Index of the first pin of the row
|
||||
* @param count Number of pins in the row
|
||||
* @param below True to print the labels below the row, false to print them above it
|
||||
* @param silkColor Silkscreen text color
|
||||
*/
|
||||
protected void drawFlatRowPinLabels(Graphics2D g2d, double x, double y, double[][] offsets,
|
||||
int startIndex, int count, boolean below, Color silkColor) {
|
||||
if (offsets == null || offsets.length == 0) {
|
||||
return;
|
||||
}
|
||||
double labelOffset = PIN_ROW_FLAT_LABEL_OFFSET.convertToPixels();
|
||||
|
||||
g2d.setColor(silkColor);
|
||||
g2d.setFont(PIN_ROW_FLAT_FONT);
|
||||
|
||||
for (int i = startIndex; i < startIndex + count && i < offsets.length; i++) {
|
||||
String label = getSilkPinLabel(i);
|
||||
if (label == null || label.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
double pinX = x + offsets[i][0];
|
||||
double pinY = y + offsets[i][1] + (below ? labelOffset : -labelOffset);
|
||||
StringUtils.drawCenteredText(g2d, label, pinX, pinY, HorizontalAlignment.CENTER,
|
||||
VerticalAlignment.CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to draw a complete PCB terminal block (green body + inner compartment lines + screw lugs)
|
||||
* that matches the visuals of the PCBTerminalBlock component.
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
import org.diylc.common.HorizontalAlignment;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.common.VerticalAlignment;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.diylc.components.MakerBoardPainter;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.annotations.BomPolicy;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.KeywordPolicy;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "Nokia 5110 LCD", category = "Displays & Outputs",
|
||||
author = "Branislav Stojkovic",
|
||||
description = "84x48 graphic LCD module with a PCD8544 controller and an SPI interface",
|
||||
instanceNamePrefix = "LCD", zOrder = IDIYComponent.COMPONENT,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class Nokia5110LCD extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Color PCB_BLUE = Color.decode("#15548C");
|
||||
public static Color BEZEL_COLOR = LIGHT_METAL_COLOR;
|
||||
public static Color BEZEL_BORDER_COLOR = LIGHT_METAL_COLOR.darker();
|
||||
public static Color LCD_COLOR = Color.decode("#6B7668");
|
||||
public static Color LCD_BORDER_COLOR = LCD_COLOR.darker();
|
||||
|
||||
public static Size BOARD_WIDTH = new Size(43.8d, SizeUnit.mm);
|
||||
public static Size BOARD_LENGTH = new Size(45.8d, SizeUnit.mm);
|
||||
// The metal frame holding the glass: centred across the board and measured down from the top
|
||||
// edge, which is where the asymmetry of the module comes from -- the pin row takes up the strip
|
||||
// left at the bottom.
|
||||
public static Size BEZEL_WIDTH = new Size(40.0d, SizeUnit.mm);
|
||||
public static Size BEZEL_LENGTH = new Size(35.0d, SizeUnit.mm);
|
||||
public static Size BEZEL_TOP_OFFSET = new Size(4.5d, SizeUnit.mm);
|
||||
// The glass sits lower in the frame than it is centred, so it is placed from the top edge of the
|
||||
// board rather than centred in the bezel.
|
||||
public static Size DISPLAY_WIDTH = new Size(37.0d, SizeUnit.mm);
|
||||
public static Size DISPLAY_LENGTH = new Size(27.0d, SizeUnit.mm);
|
||||
public static Size DISPLAY_TOP_OFFSET = new Size(11.0d, SizeUnit.mm);
|
||||
// Centre-to-centre in each direction, which is how the pattern is specified and what a builder
|
||||
// drills.
|
||||
public static Size MOUNTING_HOLE_SPACING_X = new Size(34.5d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_SPACING_Y = new Size(41.0d, SizeUnit.mm);
|
||||
public static Size MOUNTING_HOLE_SIZE = new Size(3.0d, SizeUnit.mm);
|
||||
// clearance from the bottom edge to the pin row
|
||||
public static Size HEADER_OFFSET = new Size(3.5d, SizeUnit.mm);
|
||||
public static Size CORNER_RADIUS = new Size(1.5d, SizeUnit.mm);
|
||||
// gap between the top left mounting hole and the part name printed beside it
|
||||
public static Size SILK_TEXT_GAP = new Size(1.0d, SizeUnit.mm);
|
||||
|
||||
public static final String[] PIN_NAMES =
|
||||
new String[] {"RST", "CE", "DC", "DIN", "CLK", "VCC", "BL", "GND"};
|
||||
|
||||
public Nokia5110LCD() {
|
||||
super();
|
||||
this.bodyColor = PCB_BLUE;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
if (index >= 0 && index < PIN_NAMES.length) {
|
||||
return PIN_NAMES[index];
|
||||
}
|
||||
return "Pin " + (index + 1);
|
||||
}
|
||||
|
||||
/** Left edge of the board, derived so that the pin row sits centred on the bottom edge. */
|
||||
private double getBoardX(double x) {
|
||||
double span = (PIN_NAMES.length - 1) * PIN_SPACING.convertToPixels();
|
||||
return x - (BOARD_WIDTH.convertToPixels() - span) / 2.0;
|
||||
}
|
||||
|
||||
/** Top edge of the board, which is a whole board above the pin row rather than below it. */
|
||||
private double getBoardY(double y) {
|
||||
return y - BOARD_LENGTH.convertToPixels() + HEADER_OFFSET.convertToPixels();
|
||||
}
|
||||
|
||||
private double[][] getRelativeOffsets() {
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
double[][] relativeOffsets = new double[PIN_NAMES.length][2];
|
||||
for (int i = 0; i < relativeOffsets.length; i++) {
|
||||
relativeOffsets[i][0] = i * spacing;
|
||||
relativeOffsets[i][1] = 0;
|
||||
}
|
||||
return relativeOffsets;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControlPoints() {
|
||||
rotatePoints(controlPoints[0], getRelativeOffsets());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
double radius = CORNER_RADIUS.convertToPixels();
|
||||
return new RoundRectangle2D.Double(getBoardX(p0.getX()), getBoardY(p0.getY()),
|
||||
BOARD_WIDTH.convertToPixels(), BOARD_LENGTH.convertToPixels(), radius, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||
Project project, IDrawingObserver drawingObserver) {
|
||||
if (checkPointsClipped(g2d.getClip())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point2D p0 = controlPoints[0];
|
||||
double x = p0.getX();
|
||||
double y = p0.getY();
|
||||
|
||||
AffineTransform oldTx = g2d.getTransform();
|
||||
if (orientation != Orientation.DEFAULT) {
|
||||
g2d.rotate(orientation.toRadians(), x, y);
|
||||
}
|
||||
|
||||
double boardW = BOARD_WIDTH.convertToPixels();
|
||||
double boardH = BOARD_LENGTH.convertToPixels();
|
||||
double boardX = getBoardX(x);
|
||||
double boardY = getBoardY(y);
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
|
||||
Composite oldComposite = applyAlpha(g2d, componentState);
|
||||
|
||||
drawingObserver.startTracking();
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
|
||||
g2d.fill(boardShape);
|
||||
drawingObserver.stopTracking();
|
||||
|
||||
g2d.setColor(getFinalBorderColor(componentState, outlineMode));
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
|
||||
g2d.draw(boardShape);
|
||||
|
||||
if (!outlineMode) {
|
||||
double holeInsetX = (boardW - MOUNTING_HOLE_SPACING_X.convertToPixels()) / 2.0;
|
||||
double holeInsetY = (boardH - MOUNTING_HOLE_SPACING_Y.convertToPixels()) / 2.0;
|
||||
double holeSize = MOUNTING_HOLE_SIZE.convertToPixels();
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInsetX, boardY + holeInsetY, holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInsetX, boardY + holeInsetY,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + holeInsetX, boardY + boardH - holeInsetY,
|
||||
holeSize);
|
||||
MakerBoardPainter.drawMountingHole(g2d, boardX + boardW - holeInsetX,
|
||||
boardY + boardH - holeInsetY, holeSize);
|
||||
|
||||
// The part name goes in the strip above the frame, starting clear of the top left hole and
|
||||
// level with it.
|
||||
g2d.setColor(SILK_COLOR);
|
||||
g2d.setFont(SILK_FONT_LARGE);
|
||||
StringUtils.drawCenteredText(g2d, "Nokia 5110",
|
||||
boardX + holeInsetX + holeSize / 2.0 + SILK_TEXT_GAP.convertToPixels(),
|
||||
boardY + holeInsetY, HorizontalAlignment.LEFT, VerticalAlignment.CENTER);
|
||||
|
||||
double bezelW = BEZEL_WIDTH.convertToPixels();
|
||||
double bezelH = BEZEL_LENGTH.convertToPixels();
|
||||
double bezelX = boardX + (boardW - bezelW) / 2.0;
|
||||
double bezelY = boardY + BEZEL_TOP_OFFSET.convertToPixels();
|
||||
|
||||
g2d.setColor(BEZEL_COLOR);
|
||||
g2d.fill(new Rectangle2D.Double(bezelX, bezelY, bezelW, bezelH));
|
||||
g2d.setColor(BEZEL_BORDER_COLOR);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.draw(new Rectangle2D.Double(bezelX, bezelY, bezelW, bezelH));
|
||||
|
||||
double displayW = DISPLAY_WIDTH.convertToPixels();
|
||||
double displayH = DISPLAY_LENGTH.convertToPixels();
|
||||
double displayX = boardX + (boardW - displayW) / 2.0;
|
||||
double displayY = boardY + DISPLAY_TOP_OFFSET.convertToPixels();
|
||||
double displayRadius = CORNER_RADIUS.convertToPixels();
|
||||
|
||||
g2d.setColor(LCD_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(displayX, displayY, displayW, displayH, displayRadius,
|
||||
displayRadius));
|
||||
g2d.setColor(LCD_BORDER_COLOR);
|
||||
g2d.draw(new RoundRectangle2D.Double(displayX, displayY, displayW, displayH, displayRadius,
|
||||
displayRadius));
|
||||
|
||||
// The module prints its pin names in the strip below the header. They lie flat along the row
|
||||
// rather than standing on end across it the way the Arduino boards print theirs, because the
|
||||
// strip is 3.5 mm deep and a label stood on end needs 4.7 mm: one label offset plus the
|
||||
// length of the longest name.
|
||||
drawFlatRowPinLabels(g2d, x, y, getRelativeOffsets(), 0, PIN_NAMES.length, true,
|
||||
SILK_COLOR);
|
||||
}
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
drawPinHeader(g2d, 0, controlPoints.length, outlineMode, drawingObserver);
|
||||
|
||||
g2d.setComposite(oldComposite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
g2d.setColor(PCB_BLUE);
|
||||
g2d.fill(new RoundRectangle2D.Double(2, 1, width - 4, height - 5, 3, 3));
|
||||
g2d.setColor(PCB_BLUE.darker());
|
||||
g2d.draw(new RoundRectangle2D.Double(2, 1, width - 4, height - 5, 3, 3));
|
||||
|
||||
g2d.setColor(BEZEL_COLOR);
|
||||
g2d.fillRect(4, 3, width - 8, height - 10);
|
||||
|
||||
g2d.setColor(LCD_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(5, 7, width - 10, height - 15, 2, 2));
|
||||
|
||||
g2d.setColor(PIN_COLOR);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
g2d.fillRect(width / 2 - 5 + i * 3, height - 4, 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
*
|
||||
* DIY Layout Creator (DIYLC).
|
||||
* Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
*
|
||||
* This file is part of DIYLC.
|
||||
*
|
||||
* DIYLC is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* DIYLC is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.diylc.components.displays;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.components.AbstractMakerBoard;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Geometry tests for the Nokia 5110 module. The part has no variants, so what is worth pinning
|
||||
* down is the one thing its layout does differently from the other display boards: the pin row is
|
||||
* on the bottom edge, which means the board grows upwards from control point zero and every
|
||||
* feature is measured down from an edge the control points do not touch.
|
||||
*/
|
||||
public class Nokia5110LCDTest {
|
||||
|
||||
private static final double PX_PER_MM = 200.0d / 25.4d;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
ConfigurationManager.getInstance().initialize("diylc");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static double mm(double px) {
|
||||
return px / PX_PER_MM;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boardIsTheModuleOutline() {
|
||||
Rectangle2D bounds = new Nokia5110LCD().getBodyShape().getBounds2D();
|
||||
Assert.assertEquals("width", 43.8d, mm(bounds.getWidth()), 0.01d);
|
||||
Assert.assertEquals("length", 45.8d, mm(bounds.getHeight()), 0.01d);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controlPointsAreTheEightPinsInOrder() {
|
||||
Nokia5110LCD display = new Nokia5110LCD();
|
||||
String[] expected = new String[] {"RST", "CE", "DC", "DIN", "CLK", "VCC", "BL", "GND"};
|
||||
Assert.assertEquals(expected.length, display.getControlPointCount());
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
Assert.assertEquals("pin " + i, expected[i], display.getControlPointNodeName(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pinsSitAtHeaderPitchAlongOneRow() {
|
||||
Nokia5110LCD display = new Nokia5110LCD();
|
||||
for (int i = 1; i < display.getControlPointCount(); i++) {
|
||||
Point2D previous = display.getControlPoint(i - 1);
|
||||
Point2D current = display.getControlPoint(i);
|
||||
Assert.assertEquals("pitch at pin " + i, 2.54d, mm(current.getX() - previous.getX()), 0.01d);
|
||||
Assert.assertEquals("pin " + i + " left the row", previous.getY(), current.getY(), 0.01d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The row is centred across the board and stands clear of the bottom edge rather than of the top
|
||||
* one, which is the easiest thing to get backwards when the board is placed from its header.
|
||||
*/
|
||||
@Test
|
||||
public void headerIsCentredOnTheBottomEdge() {
|
||||
Nokia5110LCD display = new Nokia5110LCD();
|
||||
Rectangle2D board = display.getBodyShape().getBounds2D();
|
||||
Point2D first = display.getControlPoint(0);
|
||||
Point2D last = display.getControlPoint(display.getControlPointCount() - 1);
|
||||
|
||||
Assert.assertEquals("left margin", board.getMaxX() - last.getX(), first.getX() - board.getX(),
|
||||
0.01d);
|
||||
Assert.assertEquals("clearance from the bottom edge", 3.5d, mm(board.getMaxY() - first.getY()),
|
||||
0.01d);
|
||||
Assert.assertTrue("the row sits below the glass", first.getY() > board.getCenterY());
|
||||
}
|
||||
|
||||
/**
|
||||
* The glass is placed from the top edge of the board rather than centred in the frame, so
|
||||
* nothing keeps the two in step on its own: both offsets have to be checked against each other.
|
||||
*/
|
||||
@Test
|
||||
public void glassSitsInsideTheBezel() {
|
||||
double bezelTop = Nokia5110LCD.BEZEL_TOP_OFFSET.convertToPixels();
|
||||
double bezelBottom = bezelTop + Nokia5110LCD.BEZEL_LENGTH.convertToPixels();
|
||||
double glassTop = Nokia5110LCD.DISPLAY_TOP_OFFSET.convertToPixels();
|
||||
double glassBottom = glassTop + Nokia5110LCD.DISPLAY_LENGTH.convertToPixels();
|
||||
|
||||
Assert.assertTrue("glass reaches above the bezel", glassTop >= bezelTop);
|
||||
Assert.assertTrue("glass reaches below the bezel", glassBottom <= bezelBottom);
|
||||
Assert.assertTrue("glass is wider than the bezel", Nokia5110LCD.DISPLAY_WIDTH
|
||||
.convertToPixels() <= Nokia5110LCD.BEZEL_WIDTH.convertToPixels());
|
||||
Assert.assertTrue("bezel runs off the bottom of the board",
|
||||
bezelBottom <= Nokia5110LCD.BOARD_LENGTH.convertToPixels());
|
||||
}
|
||||
|
||||
/**
|
||||
* The hole pattern is 34.5 x 41 mm on a 43.8 x 45.8 mm board, so every drill sits 2.4 mm or more
|
||||
* from the two edges nearest it and a 3 mm hole clears both. This is what catches the outline
|
||||
* being entered the wrong way round: with the two figures swapped the vertical pair is 1.4 mm
|
||||
* from the edge and the hole breaches it.
|
||||
*/
|
||||
@Test
|
||||
public void mountingHolesClearTheBoardEdges() {
|
||||
double insetX = (Nokia5110LCD.BOARD_WIDTH.convertToPixels()
|
||||
- Nokia5110LCD.MOUNTING_HOLE_SPACING_X.convertToPixels()) / 2.0;
|
||||
double insetY = (Nokia5110LCD.BOARD_LENGTH.convertToPixels()
|
||||
- Nokia5110LCD.MOUNTING_HOLE_SPACING_Y.convertToPixels()) / 2.0;
|
||||
double radius = Nokia5110LCD.MOUNTING_HOLE_SIZE.convertToPixels() / 2.0;
|
||||
|
||||
Assert.assertTrue("holes breach the side edges", insetX >= radius);
|
||||
Assert.assertTrue("holes breach the top and bottom edges", insetY >= radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* The silkscreen stands in the strip between the pin row and the bottom edge, which is the one
|
||||
* place on this board where a label can run out of room: the strip is only as deep as the
|
||||
* header's clearance from the edge.
|
||||
*/
|
||||
@Test
|
||||
public void silkLabelsStayInTheStripBelowThePins() {
|
||||
Assert.assertTrue("the labels sit past the bottom edge",
|
||||
AbstractMakerBoard.PIN_ROW_FLAT_LABEL_OFFSET.convertToPixels() < Nokia5110LCD.HEADER_OFFSET
|
||||
.convertToPixels());
|
||||
Assert.assertTrue("the labels sit on top of the pins",
|
||||
AbstractMakerBoard.PIN_ROW_FLAT_LABEL_OFFSET.convertToPixels() > AbstractMakerBoard.PIN_SIZE
|
||||
.convertToPixels());
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,7 @@ public class MakerComponentsTest {
|
||||
WS2812BRing.class,
|
||||
WS2812BStrip.class,
|
||||
WS2812BJewel.class,
|
||||
Nokia5110LCD.class,
|
||||
|
||||
// Electro-Mechanical
|
||||
BatteryHolder18650.class
|
||||
|
||||
@@ -10,7 +10,7 @@ Companion to: `maker-board-roadmap.md` (which covers `org.diylc.components.micro
|
||||
## 1. Goal
|
||||
|
||||
The first maker slice shipped ten controller boards in 6.5.0. Forty-one further components sit in
|
||||
the tree with their `@ComponentDescriptor` annotations commented out, across four packages:
|
||||
the tree with theiryou `@ComponentDescriptor` annotations commented out, across four packages:
|
||||
`displays` (7), `sensors` (9), `modules` (21) and `robotics` (4). None of them is discoverable, and
|
||||
`MakerComponentsTest` lists all forty-one in `unreleasedMakerComponentClasses`.
|
||||
|
||||
@@ -40,15 +40,15 @@ robotics are deliberately deferred; §10 records why.
|
||||
|
||||
## 3. Current coverage
|
||||
|
||||
Nine classes now, all `category = "Displays & Outputs"` and all `enableCache = true`. Seven are the
|
||||
drafts this slice set out to release; `WS2812BStrip` (§6.1) and `LEDBarGraph` (§6.6) are §6's
|
||||
additions so far.
|
||||
Eleven classes now, all `category = "Displays & Outputs"` and all `enableCache = true`. Seven are
|
||||
the drafts this slice set out to release; `WS2812BStrip` (§6.1), `LEDBarGraph` (§6.6),
|
||||
`WS2812BJewel` (§6.7) and `Nokia5110LCD` (§6.5) are §6's additions so far.
|
||||
|
||||
Two of the generalisations that held when this section was written no longer do, and both are worth
|
||||
keeping visible rather than quietly dropping. **Not every class extends `AbstractMakerBoard`**:
|
||||
`LEDBarGraph` is a DIP-outline part rather than a board, so it follows `DIL_IC` on
|
||||
`AbstractLabeledComponent` and carries its own transformer, inheriting none of the maker helpers.
|
||||
**All nine do set `bomPolicy = SHOW_ONLY_TYPE_NAME`**, but the bar graph only does so because it was
|
||||
**All eleven do set `bomPolicy = SHOW_ONLY_TYPE_NAME`**, but the bar graph only does so because it was
|
||||
caught: it had inherited `DIL_IC`'s silence, and the annotation default is `SHOW_ALL_NAMES`, so
|
||||
saying nothing had quietly opted it out of the category's convention. It also had to override
|
||||
`getValueForDisplay` by hand to get its segment count into the value column, because `BomMaker`
|
||||
@@ -70,6 +70,7 @@ happened to come back true, and only after a fix.
|
||||
| `LEDBarGraph` | 8 / 10 / 12 segment | **New in this slice (§6.6), not a draft**, and the only class here that is not an `AbstractMakerBoard` — it is a DIP-outline part, so it follows `DIL_IC` on `AbstractLabeledComponent` with a sibling transformer. The package follows from the segment count rather than being fixed: anode and cathode per segment gives 16 / 20 / 24 pins, and the body lengthens one pin pitch per segment (20.32 / 25.4 / 30.48 mm) at a constant 10.3 mm width, with rows 7.62 mm apart. Unlike a DIP IC the body spans the rows instead of sitting between them, because the pins leave the underside rather than the sides, so the plastic covers them entirely — they are drawn first and painted over, never skipped, or they would drop out of the conductive areas. Each segment is centred on the pin pair that drives it, taken from the two control points rather than by dividing the body, which is also what makes it orientation-proof. Colour is a rule rather than a property — one red at the top, two yellow, green for the rest — so the green band grows with the count; the cost is that a single-colour bar can no longer be drawn. Pins are round and drawn over the package: hidden on the real part, but a pin that cannot be seen cannot be positioned against a board. Covered by `LEDBarGraphTest`, whose `bodyCoversEveryPin` replaces an earlier assertion that encoded the copied DIP arrangement and therefore passed while the component was wrong, and whose `segmentsAreCentredOnTheirPins` catches a placement that was inside the body and still off its pins. |
|
||||
| `WS2812BStrip` | 30 / 60 / 144 LED/m, 1-144 LEDs | **New in this slice (§6.1), not a draft** — so unlike its neighbours this row describes a part rather than a rehabilitation. Everything follows from the density, which is how tape is sold: pitch is 1000 mm over the count (33.33 / 16.67 / 6.94 mm), length is linear in the LED count, and four control points hold at every density and count because the LEDs are drawn rather than wired. Two figures are not measured: the tape width (10 mm for the 30 and 60, 12 mm for the 144) is the plan's stated norm, and the lead-in that keeps the end pads clear of the first and last package is derived from the pad and package footprints rather than taken from a real tape. Covered by `WS2812BStripTest`, whose pad-clearance assertions exist because that geometry was got wrong three times before it was measured. |
|
||||
| `WS2812BJewel` | none (a single part) | **New in this slice (§6.7), not a draft.** A sibling of `WS2812BRing` rather than another `RingSize` constant, per D2: it shares the part family and the pad footprint but not the footprint family. It is a solid 23 mm disc with no inner rim, six LEDs on a 16 mm circle around a seventh in the middle, five pads on a 9 mm circle *inside* the LED ring rather than inset from the rim, and two 3 mm mounting holes 19 mm apart that no ring carries — five variant-specific structures, which is a different part and not a variant. Every figure is maintainer-supplied. The pad arrangement is the part that could silently have been wrong: five pads share six gaps between the ring LEDs, so one gap carries none, and it works out to the gap pointing due left, which is where a mounting hole sits. `WS2812BJewelTest` pins that down, because every count and radius assertion passes whichever gap is left empty. Its packages — and the rings', which this changed too — are drawn turned to follow their own radius the way they are mounted; the centre package has no radius and stays square to the board. |
|
||||
| `Nokia5110LCD` | none (a single part) | **New in this slice (§6.5), not a draft.** Every figure is maintainer-supplied and the whole outline is measured: a 43.8 x 45.8 mm board, a 40 x 35 mm bezel 4.5 mm down from the top edge, a 37 x 27 mm glass 11 mm down, and 3 mm mounting holes on a 34.5 x 41 mm pattern. It is the first display board whose header is on the **bottom** edge, so the board grows upwards from control point zero rather than down from it, and every feature is measured from an edge no control point touches. The glass is placed from the top edge rather than centred in the bezel, because it is not centred in it -- 6.5 mm of frame above, 1.5 mm below. It is also the first in the package to carry silkscreen: the pin names lie flat along the row under the header rather than standing on end like the Arduino boards', and the part name sits in the top left corner beside the mounting hole, where the module prints it (§6.5). One figure is unsourced: the 3.5 mm clearance from the bottom edge to the pin row. |
|
||||
| `WS2812BStick` | none | Hardcoded to 8 LEDs, with every LED dimension in raw pixels (28 × 28 package, 68 px insets), so the LEDs do not scale with the board. See the node-name bug below. All since corrected: a `Size`-based 51.1 × 10.22 mm board, real 5 mm 5050 packages off the shared `RGB_LED_SIZE`, pads reordered and renamed for uniqueness, and the LED row respread between the pad columns and drawn lit from the shared colour wheel (§11.6). |
|
||||
|
||||
### 3.1 Two defects that are bugs, not style
|
||||
@@ -479,11 +480,55 @@ The plan this was built from, kept for the record:
|
||||
Tests: per-variant size, header positions at both ends in both variants, draw smoke.
|
||||
**~0.5-1 day.**
|
||||
|
||||
### 6.5 Nokia 5110 LCD — new class `Nokia5110LCD`
|
||||
### 6.5 Nokia 5110 LCD — new class `Nokia5110LCD` — **done**
|
||||
|
||||
Still in every starter kit, a distinct 8-pin footprint, and simple geometry: a board with a large
|
||||
glass area and a single 1x8 header. PCD8544 controller, 84x48 pixels, `RST CE DC DIN CLK VCC BL GND`.
|
||||
One class, no variants. **~0.5 day.**
|
||||
Two files, `Nokia5110LCD` and `Nokia5110LCDTest`, built entirely from maintainer figures: a
|
||||
43.8 x 45.8 mm board, a 40 x 35 mm bezel in `LIGHT_METAL_COLOR` centred across it and 4.5 mm down
|
||||
from the top edge, a 37 x 27 mm rounded glass 11 mm down, 3 mm mounting holes on a 34.5 x 41 mm pattern,
|
||||
and the eight pins `RST CE DC DIN CLK VCC BL GND` centred on the bottom edge. No variants, so no enum and no
|
||||
`getVariantLabel` override; the BOM value column is empty, as it is for the other parts sold in one
|
||||
form.
|
||||
|
||||
**It is the first display board placed from its bottom edge, and that is the whole of what was new
|
||||
here.** Every other board in the package hangs below a header on its top edge, so `getBoardY` is a
|
||||
small clearance subtracted from the control point. Here the board is a whole board *above* the
|
||||
pins, and the bezel, the glass and the hole rows are all measured from the top edge, which no
|
||||
control point touches. `headerIsCentredOnTheBottomEdge` is the test that would catch the sign of
|
||||
that getting flipped, since a board drawn downwards from the pins still passes every size and pitch
|
||||
assertion.
|
||||
|
||||
The glass is placed from the top edge rather than centred in the bezel, because it is not centred
|
||||
in it: 6.5 mm of frame above it against 1.5 mm below. Centring it would have been the cheaper
|
||||
expression and would have put it 2.5 mm high, which is why `glassSitsInsideTheBezel` checks the two
|
||||
offsets against each other rather than checking each against the board.
|
||||
|
||||
**The outline was first entered the wrong way round, and the geometry said so before anyone looked
|
||||
at it.** Built as 45.8 wide by 43.8 long, the 41 mm vertical hole pattern left 1.4 mm to each edge,
|
||||
so a 3 mm hole overran the board by 0.1 mm -- a third of a pixel at 1:1, invisible in the render and
|
||||
passing every test that existed, since all of them compared features against each other rather than
|
||||
against the edges. Turned the right way round, 43.8 x 45.8, every drill sits 2.4 mm or more from the
|
||||
two edges nearest it and the bottom strip grows from 4.8 mm to 6.8 mm, which is where the header and
|
||||
its silkscreen live. `mountingHolesClearTheBoardEdges` now asserts that clearance, and it fails if
|
||||
the two figures are swapped again; the hole pattern itself is modelled as centre-to-centre spacing
|
||||
rather than an edge inset, following `OLEDDisplay`, because that is how it is specified and what a
|
||||
builder drills.
|
||||
|
||||
**The pin names are printed, and they lie flat along the row.** This is the first part in the package to
|
||||
carry silkscreen at all -- the Ring, the Stick and the Strip all had theirs dropped -- and the
|
||||
reason it can is the 3.5 mm strip between the pin row and the bottom edge. `drawRowPinLabels` turns
|
||||
its labels on their side, which needs `PIN_ROW_LABEL_OFFSET` plus the length of the longest name:
|
||||
2.0 + 2.7 = 4.7 mm, more than the strip has. So `AbstractMakerBoard` gained a sibling,
|
||||
`drawFlatRowPinLabels`, which leaves them lying in the row's own direction. Flat, the constraint
|
||||
flips from depth to width -- each label has the 2.54 mm pin pitch to fit into, where the widest, `GND`,
|
||||
measures 2.67 mm at the rotated labels' 9 pt -- so the helper carries its own 8 pt font, at which
|
||||
the same label is 2.29 mm and clears its neighbours by 0.25 mm. Both figures are `FontMetrics`
|
||||
measurements rather than estimates, and the margin is deliberately slim: 7 pt was legible and left
|
||||
0.6 mm between labels, and the maintainer chose the larger size on sight of the render. The names
|
||||
come from `getSilkPinLabel` like every other board's, so nothing duplicates `PIN_NAMES`.
|
||||
|
||||
The one **unsourced number in the class** is the 3.5 mm clearance from the bottom edge to the pin
|
||||
row, set by eye off the render so that the silkscreen has room to stand under the header. It places the row, and through the row the
|
||||
silkscreen, so it is worth replacing with a measurement if the board is ever in hand.
|
||||
|
||||
### 6.6 LED Bar Graph — new class — **done**
|
||||
|
||||
@@ -683,8 +728,9 @@ part name, then the variant list).
|
||||
5. **§7** tests alongside each of the above, not after.
|
||||
6. **§8** regression samples and the `update.xml` block, last.
|
||||
|
||||
Ten palette entries at the end of it: the seven existing, plus the strip, the bar graph and the
|
||||
Jewel, plus the Nokia and the panel if appetite holds.
|
||||
Eleven palette entries so far: the seven existing, plus the strip, the bar graph, the Jewel and the
|
||||
Nokia, plus the panel if appetite holds. The `update.xml` 6.7.0 block still lists only the original
|
||||
seven -- the strip, the bar graph, the Jewel and the Nokia have no entry yet.
|
||||
|
||||
## 10. Later slices, and why they are later
|
||||
|
||||
|
||||
Reference in New Issue
Block a user