nano labels

This commit is contained in:
Bane Stojkovic
2026-09-08 09:09:13 +02:00
parent 7ed8403c41
commit 99b1ee0c5d
3 changed files with 138 additions and 11 deletions
@@ -300,25 +300,44 @@ public abstract class AbstractMakerBoard extends AbstractTransparentComponent<Vo
*/
protected void drawPinLabels(Graphics2D g2d, double x, double y, double[][] offsets, Color silkColor) {
if (offsets == null || offsets.length == 0) return;
drawPinLabels(g2d, x, y, offsets, offsets.length / 2, silkColor);
}
/**
* Variant for boards whose two pin rows do not take up the whole control point array, such as a
* board that carries an ICSP block or a connector after them. Control points past the two rows
* are left unlabelled.
*
* @param g2d Graphics2D context (already transformed for board orientation)
* @param x Unrotated top-left pin X coordinate (P0.getX())
* @param y Unrotated top-left pin Y coordinate (P0.getY())
* @param offsets Array of [x, y] relative offsets for all control points
* @param pinsPerRow Number of pins in each of the two rows
* @param silkColor Silkscreen text color
*/
protected void drawPinLabels(Graphics2D g2d, double x, double y, double[][] offsets,
int pinsPerRow, Color silkColor) {
if (offsets == null || offsets.length == 0) return;
double labelOffset = PIN_LABEL_OFFSET.convertToPixels();
int count = offsets.length;
int half = count / 2;
g2d.setColor(silkColor);
g2d.setFont(PIN_FONT);
for (int i = 0; i < count; i++) {
for (int i = 0; i < 2 * pinsPerRow && i < offsets.length; i++) {
String label = getSilkPinLabel(i);
if (label == null || label.isEmpty()) {
continue;
}
boolean leftRow = i < pinsPerRow;
double pinX = x + offsets[i][0];
double pinY = y + offsets[i][1];
double textX = (i < half) ? (pinX + labelOffset) : (pinX - labelOffset);
double textX = leftRow ? (pinX + labelOffset) : (pinX - labelOffset);
double textY = pinY;
// the two rows are mirrored so that each label reads away from the pin it belongs to, the
// way a board prints them
AffineTransform oldLabelTx = g2d.getTransform();
g2d.rotate(Math.PI / 2, textX, textY);
g2d.rotate(leftRow ? Math.PI / 2 : -Math.PI / 2, textX, textY);
StringUtils.drawCenteredText(g2d, label, textX, textY, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
g2d.setTransform(oldLabelTx);
}
@@ -203,6 +203,42 @@ public class ArduinoNano extends AbstractMakerBoard {
"B0", "3.3V", "D13 (~, SCK/GPIO48)"
};
// What the boards print next to the pads, which is shorter than the node name: the two grounds
// and the two resets are printed alike, the supply is "3V3", and the function annotations the
// node names carry are not on the silkscreen at all. The classic Nano's ICSP block and the R4's
// Qwiic socket are printed nowhere, so these arrays cover the two pin rows only.
public static final String[] SILK_NAMES = new String[] {
// Left row (0..14, top to bottom)
"TX1", "RX0", "RST", "GND", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12",
// Right row (15..29, top to bottom)
"VIN", "GND", "RST", "5V", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "AREF", "3V3", "D13"
};
// The RP2040 Connect puts the BOOTSEL pad where the other boards repeat RESET
public static final String[] SILK_NAMES_RP2040 = new String[] {
// Left row (0..14, top to bottom)
"TX1", "RX0", "RST", "GND", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12",
// Right row (15..29, top to bottom)
"VIN", "GND", "REC", "5V", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "AREF", "3V3", "D13"
};
// The R4 uses that same pad for boot mode selection
public static final String[] SILK_NAMES_R4 = new String[] {
// Left row (0..14, top to bottom)
"TX1", "RX0", "RST", "GND", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12",
// Right row (15..29, top to bottom)
"VIN", "GND", "BOOT", "5V", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "AREF", "3V3", "D13"
};
// The Nano ESP32 has the B1 / B0 boot-mode pads where the others carry the second RESET and
// AREF, and its 5V pad is fed straight from the USB bus
public static final String[] SILK_NAMES_ESP32 = new String[] {
// Left row (0..14, top to bottom)
"TX1", "RX0", "RST", "GND", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12",
// Right row (15..29, top to bottom)
"VIN", "GND", "B1", "VUSB", "A7", "A6", "A5", "A4", "A3", "A2", "A1", "A0", "B0", "3V3", "D13"
};
protected NanoVersion version = NanoVersion.CLASSIC;
protected boolean headers = false;
@@ -246,6 +282,28 @@ public class ArduinoNano extends AbstractMakerBoard {
}
}
private String[] getSilkNames() {
switch (getVersion()) {
case NANO_RP2040_CONNECT:
return SILK_NAMES_RP2040;
case NANO_ESP32:
return SILK_NAMES_ESP32;
case NANO_R4:
return SILK_NAMES_R4;
default:
return SILK_NAMES;
}
}
@Override
protected String getSilkPinLabel(int index) {
String[] silkNames = getSilkNames();
if (index >= 0 && index < silkNames.length) {
return silkNames[index];
}
return super.getSilkPinLabel(index);
}
private UsbPortType getUsbPortType() {
switch (getVersion()) {
case CLASSIC:
@@ -305,9 +363,7 @@ public class ArduinoNano extends AbstractMakerBoard {
return "Pin " + (index + 1);
}
@Override
protected void updateControlPoints() {
Point2D firstPoint = controlPoints[0];
private double[][] getRelativeOffsets() {
double spacing = PIN_SPACING.convertToPixels(); // 20px (0.10")
double rowSpacing = ROW_SPACING.convertToPixels(); // 120px (0.60")
@@ -332,8 +388,7 @@ public class ArduinoNano extends AbstractMakerBoard {
relativeOffsets[30][1] = -BOARD_MARGIN_Y.convertToPixels() + END_PART_OFFSET_Y.convertToPixels()
+ QWIIC_LENGTH.convertToPixels() / 2.0;
}
rotatePoints(firstPoint, relativeOffsets);
return;
return relativeOffsets;
}
// ICSP header (2x3 pins, 30..35) flush with top edge:
@@ -359,7 +414,12 @@ public class ArduinoNano extends AbstractMakerBoard {
// Pin 6 (GND_ICSP at col0X, inner row)
relativeOffsets[35] = new double[] {col0X, icspInnerY};
rotatePoints(firstPoint, relativeOffsets);
return relativeOffsets;
}
@Override
protected void updateControlPoints() {
rotatePoints(controlPoints[0], getRelativeOffsets());
}
@Override
@@ -539,6 +599,9 @@ public class ArduinoNano extends AbstractMakerBoard {
StringUtils.drawCenteredText(g2d, getVersion().getSilkLabel(), boardX + boardW / 2.0,
boardY + new Size(0.50d, SizeUnit.in).convertToPixels() + new Size(1.0d, SizeUnit.mm).convertToPixels(),
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
// Pad names, printed between the two rows the way they are on the board
drawPinLabels(g2d, x, y, getRelativeOffsets(), PINS_PER_ROW, SILK_COLOR);
}
// Castellated pads bite into the board edge, so they are drawn while the board rotation is
@@ -365,6 +365,51 @@ public class ArduinoNanoTest {
}
}
@Test
public void testSilkPinLabels() {
Assert.assertEquals(2 * ArduinoNano.PINS_PER_ROW, ArduinoNano.SILK_NAMES.length);
Assert.assertEquals(2 * ArduinoNano.PINS_PER_ROW, ArduinoNano.SILK_NAMES_RP2040.length);
Assert.assertEquals(2 * ArduinoNano.PINS_PER_ROW, ArduinoNano.SILK_NAMES_R4.length);
Assert.assertEquals(2 * ArduinoNano.PINS_PER_ROW, ArduinoNano.SILK_NAMES_ESP32.length);
ArduinoNano nano = new ArduinoNano();
// the silkscreen prints what is on the board, not the node name: both grounds and both resets
// are printed alike and the annotations are dropped
Assert.assertEquals("GND1", nano.getControlPointNodeName(3));
Assert.assertEquals("GND2", nano.getControlPointNodeName(16));
Assert.assertEquals("GND", nano.getSilkPinLabel(3));
Assert.assertEquals("GND", nano.getSilkPinLabel(16));
Assert.assertEquals("RST", nano.getSilkPinLabel(2));
Assert.assertEquals("RST", nano.getSilkPinLabel(17));
Assert.assertEquals("TX1", nano.getSilkPinLabel(0));
Assert.assertEquals("D3", nano.getSilkPinLabel(5));
Assert.assertEquals("3V3", nano.getSilkPinLabel(28));
// the pad the classic repeats RESET on carries the boot mode selector on the later boards
ArduinoNano rp2040 = new ArduinoNano();
rp2040.setVersion(ArduinoNano.NanoVersion.NANO_RP2040_CONNECT);
Assert.assertEquals("REC", rp2040.getSilkPinLabel(17));
ArduinoNano r4 = new ArduinoNano();
r4.setVersion(ArduinoNano.NanoVersion.NANO_R4);
Assert.assertEquals("BOOT", r4.getSilkPinLabel(17));
ArduinoNano esp32 = new ArduinoNano();
esp32.setVersion(ArduinoNano.NanoVersion.NANO_ESP32);
Assert.assertEquals("B1", esp32.getSilkPinLabel(17));
Assert.assertEquals("B0", esp32.getSilkPinLabel(27));
for (ArduinoNano.NanoVersion version : ArduinoNano.NanoVersion.values()) {
ArduinoNano board = new ArduinoNano();
board.setVersion(version);
for (int i = 0; i < board.getControlPointCount(); i++) {
Assert.assertNotNull(version + " pin " + i + " should have a silkscreen label",
board.getSilkPinLabel(i));
}
}
}
@Test
public void testAllVersionsDrawing() {
java.awt.image.BufferedImage img =