add lcd matrix, pom bump

This commit is contained in:
Bane Stojkovic
2026-10-09 12:53:35 +02:00
parent 486e83b6b5
commit 44b57f4880
6 changed files with 111 additions and 37 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.6.0</version>
<version>6.7.0</version>
</parent>
<artifactId>diylc-core</artifactId>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.6.0</version>
<version>6.7.0</version>
</parent>
<artifactId>diylc-library</artifactId>
@@ -62,8 +62,6 @@ public class CharacterLCD extends AbstractMakerBoard {
private static final long serialVersionUID = 1L;
public static Size HEADER_OFFSET = new Size(2.54d, SizeUnit.mm);
// The parallel row is not centred: its leftmost pin starts this far in from the left edge.
public static Size PARALLEL_HEADER_INSET = new Size(10.0d, SizeUnit.mm);
// The I2C backpack's four pins stand in a column against the left edge, centred on the height.
public static Size I2C_HEADER_INSET = new Size(2.5d, SizeUnit.mm);
// A 3 mm hole centred 2.5 mm in leaves only 1 mm of board outside it, which is what the part
@@ -71,6 +69,15 @@ public class CharacterLCD extends AbstractMakerBoard {
public static Size MOUNTING_HOLE_INSET = new Size(2.5d, SizeUnit.mm);
public static Size MOUNTING_HOLE_SIZE = new Size(3.0d, SizeUnit.mm);
// The HD44780 character box. Square dots on a 0.6 mm pitch make the cell 2.95 x 4.75 mm, the
// figure the datasheet quotes, so the cell is derived from the pitch rather than stored twice.
public static Size DOT_SIZE = new Size(0.55d, SizeUnit.mm);
public static Size DOT_PITCH = new Size(0.6d, SizeUnit.mm);
public static Size CHAR_PITCH_X = new Size(3.55d, SizeUnit.mm);
public static Size CHAR_PITCH_Y = new Size(5.35d, SizeUnit.mm);
public static final int DOT_COLUMNS = 5;
public static final int DOT_ROWS = 8;
public static Color PCB_GREEN = Color.decode("#1B5E20");
public static Color SCREEN_BG = Color.decode("#1E88E5");
public static Color BEZEL_COLOR = Color.decode("#212121");
@@ -80,17 +87,13 @@ public class CharacterLCD extends AbstractMakerBoard {
public static Color SCREEN_INK_DARK = Color.decode("#1B2631");
public static final String[] PIN_NAMES_I2C = new String[] {"GND", "VCC", "SDA", "SCL"};
// The bare designation the module prints, with the function in parentheses where the pin's name
// does not give it away. The silkscreen label is the part before the parenthesis, there being no
// room for more along a 0.1" pitch.
public static final String[] PIN_NAMES_PARALLEL = new String[] {
"VSS (GND)", "VCC (+5V)", "VEE (Contrast)", "RS", "RW", "E",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
"A (Backlight +)", "K (Backlight -)"
};
// The bare designations the module prints. The supply and backlight pins carry their function
// in the node name instead, there being no room for it along a 0.1" pitch.
public static final String[] SILK_NAMES_PARALLEL = new String[] {
"VSS", "VCC", "VEE", "RS", "RW", "E",
"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "A", "K"
"VSS (GND)", "VDD (+5V)", "VO (Contrast)", "RS", "R/W", "E",
"DB0", "DB1", "DB2", "DB3", "DB4", "DB5", "DB6", "DB7",
"LEDA (Backlight +)", "LEDK (Backlight -)"
};
private LCDSize lcdSize = LCDSize._16x2;
@@ -166,15 +169,6 @@ public class CharacterLCD extends AbstractMakerBoard {
return "Pin " + (index + 1);
}
@Override
protected String getSilkPinLabel(int index) {
if (lcdInterface == LCDInterface.Parallel_16Pin && index >= 0
&& index < SILK_NAMES_PARALLEL.length) {
return SILK_NAMES_PARALLEL[index];
}
return super.getSilkPinLabel(index);
}
private int getPinCount() {
return lcdInterface == LCDInterface.I2C_Backpack ? PIN_NAMES_I2C.length
: PIN_NAMES_PARALLEL.length;
@@ -185,6 +179,14 @@ public class CharacterLCD extends AbstractMakerBoard {
return CalcUtils.calculateLuminance(screenColor) < 128d ? SCREEN_INK_LIGHT : SCREEN_INK_DARK;
}
/**
* An unlit dot is the backlight seen through the cell, so it shows as a shade of the backlight
* rather than a colour of its own and follows whatever the user picks.
*/
private Color getDotColor() {
return screenColor.darker();
}
private double getBoardWidth() {
return new Size(lcdSize.getWidthMm(), SizeUnit.mm).convertToPixels();
}
@@ -194,12 +196,12 @@ public class CharacterLCD extends AbstractMakerBoard {
}
/**
* Neither header is centred: the parallel row starts a fixed distance in from the left, and the
* I2C column stands against that same edge.
* Neither header is centred: the parallel row starts a distance in from the left that the module
* itself dictates, and the I2C column stands against that same edge whatever the module.
*/
private double getBoardX(double x) {
Size inset =
lcdInterface == LCDInterface.I2C_Backpack ? I2C_HEADER_INSET : PARALLEL_HEADER_INSET;
Size inset = lcdInterface == LCDInterface.I2C_Backpack ? I2C_HEADER_INSET
: new Size(lcdSize.getParallelHeaderInsetMm(), SizeUnit.mm);
return x - inset.convertToPixels();
}
@@ -302,9 +304,11 @@ public class CharacterLCD extends AbstractMakerBoard {
g2d.setColor(screenColor);
g2d.fill(new Rectangle2D.Double(screenX, screenY, screenW, screenH));
MakerBoardPainter.drawScreenText(g2d,
new Rectangle2D.Double(screenX, screenY, screenW, screenH), getScreenInk(), getScreen(),
getName(), getValueForDisplay());
Rectangle2D screenArea = new Rectangle2D.Double(screenX, screenY, screenW, screenH);
drawCharacterMatrix(g2d, screenArea);
MakerBoardPainter.drawScreenText(g2d, screenArea, getScreenInk(), getScreen(), getName(),
getValueForDisplay());
// Only the parallel row has anywhere to print. The backpack's column sits 2.5 mm from the
// left edge with the bezel 1.4 mm further in, so a label beside it would lie across the metal
@@ -322,6 +326,42 @@ public class CharacterLCD extends AbstractMakerBoard {
g2d.setComposite(oldComposite);
}
/**
* The unlit dot grid, centred in the lit area. The matrix is narrower than the glass on both
* modules, which is why it is centred rather than inset by a margin.
*/
private void drawCharacterMatrix(Graphics2D g2d, Rectangle2D screenArea) {
double dotSize = DOT_SIZE.convertToPixels();
double dotPitch = DOT_PITCH.convertToPixels();
double charPitchX = CHAR_PITCH_X.convertToPixels();
double charPitchY = CHAR_PITCH_Y.convertToPixels();
double cellWidth = (DOT_COLUMNS - 1) * dotPitch + dotSize;
double cellHeight = (DOT_ROWS - 1) * dotPitch + dotSize;
double matrixWidth = (lcdSize.getColumns() - 1) * charPitchX + cellWidth;
double matrixHeight = (lcdSize.getRows() - 1) * charPitchY + cellHeight;
double matrixX = screenArea.getX() + (screenArea.getWidth() - matrixWidth) / 2.0;
double matrixY = screenArea.getY() + (screenArea.getHeight() - matrixHeight) / 2.0;
// The 20x4 comes to 3200 dots, so the rectangle is reused rather than allocated per dot.
Rectangle2D.Double dot = new Rectangle2D.Double(0, 0, dotSize, dotSize);
g2d.setColor(getDotColor());
for (int row = 0; row < lcdSize.getRows(); row++) {
for (int column = 0; column < lcdSize.getColumns(); column++) {
double cellX = matrixX + column * charPitchX;
double cellY = matrixY + row * charPitchY;
for (int dotRow = 0; dotRow < DOT_ROWS; dotRow++) {
for (int dotColumn = 0; dotColumn < DOT_COLUMNS; dotColumn++) {
dot.x = cellX + dotColumn * dotPitch;
dot.y = cellY + dotRow * dotPitch;
g2d.fill(dot);
}
}
}
}
}
@Override
public void drawIcon(Graphics2D g2d, int width, int height) {
g2d.setColor(PCB_GREEN);
@@ -340,36 +380,47 @@ public class CharacterLCD extends AbstractMakerBoard {
public enum LCDSize {
// Labels reach the BOM's value column, so they stay short; the dimensions are carried by the
// fields beside them. The bezel and lit area are measured off the module rather than derived as
// margins off the board, which made the window grow with the board.
_16x2("16x2", 80.0, 35.0, 72.2, 24.1, 64.5, 14.5),
_20x4("20x4", 98.0, 60.0, 96.8, 39.3, 77.0, 25.2);
// margins off the board, which made the window grow with the board. The last figure is where
// the parallel header starts, which the two modules do not agree on.
_16x2("16x2", 16, 2, 80.0, 36.0, 72.2, 24.1, 64.5, 14.5, 7.5),
_20x4("20x4", 20, 4, 98.0, 60.0, 96.8, 39.3, 77.0, 25.2, 10.0);
private final String label;
private final int columns;
private final int rows;
private final double widthMm;
private final double heightMm;
private final double bezelWidthMm;
private final double bezelHeightMm;
private final double displayWidthMm;
private final double displayHeightMm;
private final double parallelHeaderInsetMm;
LCDSize(String label, double widthMm, double heightMm, double bezelWidthMm,
double bezelHeightMm, double displayWidthMm, double displayHeightMm) {
LCDSize(String label, int columns, int rows, double widthMm, double heightMm,
double bezelWidthMm, double bezelHeightMm, double displayWidthMm, double displayHeightMm,
double parallelHeaderInsetMm) {
this.label = label;
this.columns = columns;
this.rows = rows;
this.widthMm = widthMm;
this.heightMm = heightMm;
this.bezelWidthMm = bezelWidthMm;
this.bezelHeightMm = bezelHeightMm;
this.displayWidthMm = displayWidthMm;
this.displayHeightMm = displayHeightMm;
this.parallelHeaderInsetMm = parallelHeaderInsetMm;
}
@Override public String toString() { return label; }
public int getColumns() { return columns; }
public int getRows() { return rows; }
public double getWidthMm() { return widthMm; }
public double getHeightMm() { return heightMm; }
public double getBezelWidthMm() { return bezelWidthMm; }
public double getBezelHeightMm() { return bezelHeightMm; }
public double getDisplayWidthMm() { return displayWidthMm; }
public double getDisplayHeightMm() { return displayHeightMm; }
public double getParallelHeaderInsetMm() { return parallelHeaderInsetMm; }
}
public enum LCDInterface {
@@ -91,6 +91,29 @@ public class CharacterLCDTest {
MakerBoardTestSupport.assertScreenTextIsDrawn(pale, paleBlank);
}
/**
* The dot figures and the lit-area figures are measured off the module independently, so nothing
* but this keeps them consistent: a character matrix wider or taller than the glass it sits on
* means one of the two sets has drifted.
*/
@Test
public void theCharacterMatrixFitsInsideTheLitArea() {
double cellWidth = (CharacterLCD.DOT_COLUMNS - 1) * 0.6d + 0.55d;
double cellHeight = (CharacterLCD.DOT_ROWS - 1) * 0.6d + 0.55d;
for (LCDSize lcdSize : LCDSize.values()) {
double matrixWidth = (lcdSize.getColumns() - 1) * 3.55d + cellWidth;
double matrixHeight = (lcdSize.getRows() - 1) * 5.35d + cellHeight;
Assert.assertTrue(lcdSize + " matrix is " + matrixWidth + " mm wide on a "
+ lcdSize.getDisplayWidthMm() + " mm window",
matrixWidth <= lcdSize.getDisplayWidthMm());
Assert.assertTrue(lcdSize + " matrix is " + matrixHeight + " mm tall on a "
+ lcdSize.getDisplayHeightMm() + " mm window",
matrixHeight <= lcdSize.getDisplayHeightMm());
}
}
@Test
public void drawsCleanlyInEveryState() {
for (LCDSize lcdSize : LCDSize.values()) {
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.6.0</version>
<version>6.7.0</version>
</parent>
<artifactId>diylc-swing</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>6.6.0</version>
<version>6.7.0</version>
<packaging>pom</packaging>
<name>DIY Layout Creator</name>