mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
teensy
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
*
|
||||
* 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.micro;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Font;
|
||||
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.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.EditableProperty;
|
||||
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 = "Teensy", category = "Controllers",
|
||||
author = "Branislav Stojkovic", description = "PJRC Teensy 4.0 / 4.1 Development Board (ARM Cortex-M7, 600 MHz)",
|
||||
instanceNamePrefix = "MCU", zOrder = IDIYComponent.BOARD,
|
||||
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
|
||||
enableCache = true)
|
||||
public class Teensy extends AbstractMakerBoard {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum TeensyVersion {
|
||||
Teensy_4_0("Teensy 4.0"),
|
||||
Teensy_4_1("Teensy 4.1");
|
||||
|
||||
private final String label;
|
||||
|
||||
TeensyVersion(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
// Board color matches standard green PCB (like Pi Zero)
|
||||
public static Color TEENSY_GREEN = Color.decode("#1B5E20");
|
||||
public static Color BUTTON_BODY_COLOR = Color.decode("#383838");
|
||||
public static Color BUTTON_BORDER_COLOR = Color.decode("#666666");
|
||||
public static Color BUTTON_ACTUATOR_COLOR = Color.decode("#A0A0A0");
|
||||
public static Color SILK_COLOR = Color.WHITE;
|
||||
|
||||
public static Color PAD_COLOR = GOLD_COLOR;
|
||||
public static Size HOLE_SIZE = new Size(0.7d, SizeUnit.mm);
|
||||
|
||||
// ===== Teensy 4.0 dimensions (PJRC dimensions_teensy40.png) =====
|
||||
// 35.56 mm x 17.78 mm (1.4" x 0.7")
|
||||
public static Size BOARD_WIDTH_40 = new Size(17.78d, SizeUnit.mm);
|
||||
public static Size BOARD_LENGTH_40 = new Size(35.56d, SizeUnit.mm);
|
||||
|
||||
// ===== Teensy 4.1 dimensions (PJRC dimensions_teensy41.png) =====
|
||||
// 60.96 mm x 17.78 mm (2.4" x 0.7")
|
||||
public static Size BOARD_WIDTH_41 = new Size(17.78d, SizeUnit.mm);
|
||||
public static Size BOARD_LENGTH_41 = new Size(60.96d, SizeUnit.mm);
|
||||
|
||||
// Common pitch and spacing
|
||||
// Pin 1 offset from top and left edges is 0.05" (1.27 mm)
|
||||
// Row spacing is 0.60" (15.24 mm)
|
||||
// Pin spacing along row is 0.10" (2.54 mm)
|
||||
public static Size PIN1_OFFSET_X = new Size(1.27d, SizeUnit.mm);
|
||||
public static Size PIN1_OFFSET_Y = new Size(1.27d, SizeUnit.mm);
|
||||
public static Size ROW_SPACING = new Size(15.24d, SizeUnit.mm);
|
||||
|
||||
// ===== Teensy 4.0 Pinout (34 pins total) =====
|
||||
// Left row (0..13, top to bottom): GND, 0..12
|
||||
// Right row (14..27, top to bottom): VIN, GND, 3.3V, 23..13
|
||||
// End cluster (28..32, bottom edge left to right): VBAT, 3.3V, GND, Program, On/Off
|
||||
// VUSB (33, near VIN): VUSB
|
||||
public static final String[] PIN_NAMES_40 = new String[] {
|
||||
// Left row (0..13)
|
||||
"GND", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)",
|
||||
"5 (IN2)", "6 (OUT1D)", "7 (RX2/OUT1A)", "8 (TX2/IN1)", "9 (OUT1C)", "10 (CS/MQSR)",
|
||||
"11 (MOSI/CTX1)", "12 (MISO/MQSL)",
|
||||
// Right row (14..27)
|
||||
"VIN (3.6-5.5V)", "GND", "3.3V (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)",
|
||||
"20 (A6/TX5)", "19 (A5/SCL0)", "18 (A4/SDA0)", "17 (A3/TX4/SDA1)", "16 (A2/RX4/SCL1)",
|
||||
"15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/CRX1/LED)",
|
||||
// End row (28..32, left to right along bottom edge)
|
||||
"VBAT", "3.3V (End)", "GND (End)", "Program", "On/Off",
|
||||
// VUSB (33)
|
||||
"VUSB"
|
||||
};
|
||||
|
||||
// ===== Teensy 4.1 Pinout (65 pins total) =====
|
||||
// Left row (0..23, top to bottom): GND, 0..12, 3.3V, 24..32
|
||||
// Right row (24..47, top to bottom): VIN, GND, 3.3V, 23..13, GND, 41..33
|
||||
// Middle cluster (48..52, left to right): VBAT, 3.3V, GND, Program, On/Off
|
||||
// Ethernet header (53..58, 2x3): ETH_Rx+, ETH_LED, ETH_Tx-, ETH_Rx-, ETH_GND, ETH_Tx+
|
||||
// USB Host header (59..63, 1x5): USB_5V, USB_D-, USB_D+, USB_GND1, USB_GND2
|
||||
// VUSB (64, near VIN): VUSB
|
||||
public static final String[] PIN_NAMES_41 = new String[] {
|
||||
// Left row (0..23)
|
||||
"GND", "0 (RX1/CS1/CRX2)", "1 (TX1/MISO1/CTX2)", "2 (OUT2)", "3 (LRCLK2)", "4 (BCLK2)",
|
||||
"5 (IN2)", "6 (OUT1D)", "7 (RX2/OUT1A)", "8 (TX2/IN1)", "9 (OUT1C)", "10 (CS/MQSR)",
|
||||
"11 (MOSI/CTX1)", "12 (MISO/MQSL)", "3.3V", "24 (A10/TX6/SCL2)", "25 (A11/RX6/SDA2)",
|
||||
"26 (A12/MOSI1)", "27 (A13/SCK1)", "28 (RX7)", "29 (TX7)", "30 (CRX3)", "31 (CTX3)",
|
||||
"32 (OUT1B)",
|
||||
// Right row (24..47)
|
||||
"VIN (3.6-5.5V)", "GND", "3.3V (250mA)", "23 (A9/CRX1)", "22 (A8/CTX1)", "21 (A7/RX5)",
|
||||
"20 (A6/TX5)", "19 (A5/SCL)", "18 (A4/SDA)", "17 (A3/TX4/SDA1)", "16 (A2/RX4/SCL1)",
|
||||
"15 (A1/RX3/SPDIF IN)", "14 (A0/TX3/SPDIF OUT)", "13 (SCK/LED)", "GND", "41 (A17)",
|
||||
"40 (A16)", "39 (A15/MISO1)", "38 (A14/CS1)", "37 (CS)", "36 (CS)", "35 (TX8)",
|
||||
"34 (RX8)", "33 (MCLK2)",
|
||||
// Middle cluster (48..52, left to right near push button)
|
||||
"VBAT", "3.3V (Mid)", "GND (Mid)", "Program", "On/Off",
|
||||
// Ethernet header (53..58, 2x3)
|
||||
"ETH_Rx+", "ETH_LED", "ETH_Tx-", "ETH_Rx-", "ETH_GND", "ETH_Tx+",
|
||||
// USB Host header (59..63, 1x5)
|
||||
"USB_5V", "USB_D-", "USB_D+", "USB_GND1", "USB_GND2",
|
||||
// VUSB (64)
|
||||
"VUSB"
|
||||
};
|
||||
|
||||
private TeensyVersion version = TeensyVersion.Teensy_4_0;
|
||||
|
||||
public Teensy() {
|
||||
super();
|
||||
this.bodyColor = TEENSY_GREEN;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Version")
|
||||
public TeensyVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(TeensyVersion version) {
|
||||
this.version = version;
|
||||
updateControlPoints();
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
private String[] getPinNames() {
|
||||
return (version == TeensyVersion.Teensy_4_0) ? PIN_NAMES_40 : PIN_NAMES_41;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
String[] names = getPinNames();
|
||||
if (index >= 0 && index < names.length) {
|
||||
return names[index];
|
||||
}
|
||||
return "Pin " + (index + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
double spacing = PIN_SPACING.convertToPixels();
|
||||
double rowSpacing = ROW_SPACING.convertToPixels();
|
||||
|
||||
if (version == TeensyVersion.Teensy_4_0) {
|
||||
double[][] relativeOffsets = new double[PIN_NAMES_40.length][2];
|
||||
|
||||
// Left row (0..13): X=0, Y=0..13*spacing
|
||||
for (int i = 0; i < 14; i++) {
|
||||
relativeOffsets[i][0] = 0;
|
||||
relativeOffsets[i][1] = i * spacing;
|
||||
}
|
||||
|
||||
// Right row (14..27): X=rowSpacing, Y=0..13*spacing
|
||||
for (int i = 0; i < 14; i++) {
|
||||
relativeOffsets[14 + i][0] = rowSpacing;
|
||||
relativeOffsets[14 + i][1] = i * spacing;
|
||||
}
|
||||
|
||||
// End row (28..32): 5 pins between pin 12 and pin 13 at Y=13*spacing
|
||||
for (int k = 1; k <= 5; k++) {
|
||||
relativeOffsets[28 + k - 1][0] = k * spacing;
|
||||
relativeOffsets[28 + k - 1][1] = 13 * spacing;
|
||||
}
|
||||
|
||||
// VUSB (33): near VIN at X=rowSpacing - spacing, Y=spacing
|
||||
relativeOffsets[33][0] = rowSpacing - spacing;
|
||||
relativeOffsets[33][1] = spacing;
|
||||
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
} else {
|
||||
double[][] relativeOffsets = new double[PIN_NAMES_41.length][2];
|
||||
|
||||
// Left row (0..23): X=0, Y=0..23*spacing
|
||||
for (int i = 0; i < 24; i++) {
|
||||
relativeOffsets[i][0] = 0;
|
||||
relativeOffsets[i][1] = i * spacing;
|
||||
}
|
||||
|
||||
// Right row (24..47): X=rowSpacing, Y=0..23*spacing
|
||||
for (int i = 0; i < 24; i++) {
|
||||
relativeOffsets[24 + i][0] = rowSpacing;
|
||||
relativeOffsets[24 + i][1] = i * spacing;
|
||||
}
|
||||
|
||||
// Middle cluster (48..52): 5 pins near push button at Y=17*spacing
|
||||
for (int k = 1; k <= 5; k++) {
|
||||
relativeOffsets[48 + k - 1][0] = k * spacing;
|
||||
relativeOffsets[48 + k - 1][1] = 17 * spacing;
|
||||
}
|
||||
|
||||
// Ethernet Header (53..58): 2x3 header (2.0mm pitch)
|
||||
double ethPitch = new Size(2.0d, SizeUnit.mm).convertToPixels();
|
||||
double ethCenterX = rowSpacing / 2.0;
|
||||
double ethBaseY = 5 * spacing;
|
||||
// Col 1 (left): Rx+, LED, Tx-
|
||||
relativeOffsets[53] = new double[] {ethCenterX - ethPitch / 2.0, ethBaseY - ethPitch};
|
||||
relativeOffsets[54] = new double[] {ethCenterX - ethPitch / 2.0, ethBaseY};
|
||||
relativeOffsets[55] = new double[] {ethCenterX - ethPitch / 2.0, ethBaseY + ethPitch};
|
||||
// Col 2 (right): Rx-, GND, Tx+
|
||||
relativeOffsets[56] = new double[] {ethCenterX + ethPitch / 2.0, ethBaseY - ethPitch};
|
||||
relativeOffsets[57] = new double[] {ethCenterX + ethPitch / 2.0, ethBaseY};
|
||||
relativeOffsets[58] = new double[] {ethCenterX + ethPitch / 2.0, ethBaseY + ethPitch};
|
||||
|
||||
// USB Host Header (59..63): 1x5 header (2.54mm pitch)
|
||||
for (int i = 0; i < 5; i++) {
|
||||
relativeOffsets[59 + i][0] = spacing;
|
||||
relativeOffsets[59 + i][1] = (4 + i) * spacing;
|
||||
}
|
||||
|
||||
// VUSB (64): near VIN at X=rowSpacing - spacing, Y=spacing
|
||||
relativeOffsets[64][0] = rowSpacing - spacing;
|
||||
relativeOffsets[64][1] = spacing;
|
||||
|
||||
rotatePoints(firstPoint, relativeOffsets);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getBodyShape() {
|
||||
Point2D p0 = controlPoints[0];
|
||||
double x = p0.getX();
|
||||
double y = p0.getY();
|
||||
|
||||
double boardW = (version == TeensyVersion.Teensy_4_0) ? BOARD_WIDTH_40.convertToPixels() : BOARD_WIDTH_41.convertToPixels();
|
||||
double boardH = (version == TeensyVersion.Teensy_4_0) ? BOARD_LENGTH_40.convertToPixels() : BOARD_LENGTH_41.convertToPixels();
|
||||
double pin1OffsetX = PIN1_OFFSET_X.convertToPixels();
|
||||
double pin1OffsetY = PIN1_OFFSET_Y.convertToPixels();
|
||||
|
||||
double boardX = x - pin1OffsetX;
|
||||
double boardY = y - pin1OffsetY;
|
||||
return new RoundRectangle2D.Double(boardX, boardY, boardW, boardH, 4, 4);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
Shape boardShape = getBodyShape();
|
||||
Composite oldComposite = applyAlpha(g2d, componentState);
|
||||
|
||||
drawingObserver.startTracking();
|
||||
|
||||
// Fill board body (green PCB)
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
|
||||
g2d.fill(boardShape);
|
||||
|
||||
// Board outline
|
||||
g2d.setColor(getFinalBorderColor(componentState, outlineMode));
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1.5f));
|
||||
g2d.draw(boardShape);
|
||||
|
||||
if (!outlineMode) {
|
||||
Rectangle2D boardBounds = boardShape.getBounds2D();
|
||||
double boardX = boardBounds.getX();
|
||||
double boardY = boardBounds.getY();
|
||||
double boardW = boardBounds.getWidth();
|
||||
double boardH = boardBounds.getHeight();
|
||||
double centerX = boardX + boardW / 2.0;
|
||||
|
||||
// Micro-USB connector on top edge (overhangs edge slightly)
|
||||
double usbW = new Size(7.5d, SizeUnit.mm).convertToPixels();
|
||||
double usbH = new Size(5.6d, SizeUnit.mm).convertToPixels();
|
||||
double usbX = centerX - usbW / 2.0;
|
||||
double usbY = boardY - new Size(1.0d, SizeUnit.mm).convertToPixels();
|
||||
drawMetalConnector(g2d, usbX, usbY, usbW, usbH, "USB");
|
||||
|
||||
// Main MCU chip (NXP i.MX RT1062 BGA)
|
||||
double chipW = new Size(7.5d, SizeUnit.mm).convertToPixels();
|
||||
double chipH = new Size(7.5d, SizeUnit.mm).convertToPixels();
|
||||
double chipX = centerX - chipW / 2.0;
|
||||
double chipY = (version == TeensyVersion.Teensy_4_0)
|
||||
? boardY + new Size(9.5d, SizeUnit.mm).convertToPixels()
|
||||
: boardY + new Size(19.0d, SizeUnit.mm).convertToPixels();
|
||||
drawChip(g2d, chipX, chipY, chipW, chipH, "");
|
||||
|
||||
// Pushbutton (Program button)
|
||||
double btnW = new Size(3.5d, SizeUnit.mm).convertToPixels();
|
||||
double btnH = new Size(3.0d, SizeUnit.mm).convertToPixels();
|
||||
double btnX = centerX - btnW / 2.0;
|
||||
double btnY = (version == TeensyVersion.Teensy_4_0)
|
||||
? boardY + new Size(24.5d, SizeUnit.mm).convertToPixels()
|
||||
: boardY + new Size(35.5d, SizeUnit.mm).convertToPixels();
|
||||
g2d.setColor(BUTTON_BODY_COLOR);
|
||||
g2d.fill(new RoundRectangle2D.Double(btnX, btnY, btnW, btnH, 2, 2));
|
||||
g2d.setColor(BUTTON_BORDER_COLOR);
|
||||
g2d.draw(new RoundRectangle2D.Double(btnX, btnY, btnW, btnH, 2, 2));
|
||||
g2d.setColor(BUTTON_ACTUATOR_COLOR);
|
||||
g2d.fillOval((int) (btnX + btnW / 2.0 - 2.5), (int) (btnY + btnH / 2.0 - 2.5), 5, 5);
|
||||
|
||||
// Teensy 4.1 extras: Ethernet PHY + SD card slot
|
||||
if (version == TeensyVersion.Teensy_4_1) {
|
||||
// MicroSD card slot at bottom edge
|
||||
double sdW = new Size(12.0d, SizeUnit.mm).convertToPixels();
|
||||
double sdH = new Size(14.0d, SizeUnit.mm).convertToPixels();
|
||||
double sdX = centerX - sdW / 2.0;
|
||||
double sdY = boardY + boardH - sdH + new Size(1.0d, SizeUnit.mm).convertToPixels();
|
||||
drawMetalConnector(g2d, sdX, sdY, sdW, sdH, "SD");
|
||||
}
|
||||
|
||||
// Silkscreen white outline boxes around outer pins
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
int outerRowCount = (version == TeensyVersion.Teensy_4_0) ? 14 : 24;
|
||||
double padBoxMargin = new Size(0.9d, SizeUnit.mm).convertToPixels();
|
||||
|
||||
// Left row silkscreen box
|
||||
Point2D pLeftFirst = controlPoints[0];
|
||||
Point2D pLeftLast = controlPoints[outerRowCount - 1];
|
||||
g2d.draw(new Rectangle2D.Double(
|
||||
pLeftFirst.getX() - padBoxMargin, pLeftFirst.getY() - padBoxMargin,
|
||||
padBoxMargin * 2, (pLeftLast.getY() - pLeftFirst.getY()) + padBoxMargin * 2));
|
||||
|
||||
// Right row silkscreen box
|
||||
Point2D pRightFirst = controlPoints[outerRowCount];
|
||||
Point2D pRightLast = controlPoints[outerRowCount * 2 - 1];
|
||||
g2d.draw(new Rectangle2D.Double(
|
||||
pRightFirst.getX() - padBoxMargin, pRightFirst.getY() - padBoxMargin,
|
||||
padBoxMargin * 2, (pRightLast.getY() - pRightFirst.getY()) + padBoxMargin * 2));
|
||||
|
||||
// Silkscreen "TEENSY" label
|
||||
g2d.setColor(SILK_COLOR);
|
||||
g2d.setFont(SILK_FONT);
|
||||
String silkText = (version == TeensyVersion.Teensy_4_0) ? "Teensy 4.0" : "Teensy 4.1";
|
||||
double textY = (version == TeensyVersion.Teensy_4_0)
|
||||
? chipY + chipH + new Size(2.5d, SizeUnit.mm).convertToPixels()
|
||||
: chipY + chipH + new Size(3.0d, SizeUnit.mm).convertToPixels();
|
||||
StringUtils.drawCenteredText(g2d, silkText, centerX, textY, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
}
|
||||
|
||||
drawingObserver.stopTracking();
|
||||
|
||||
g2d.setTransform(oldTx);
|
||||
|
||||
// Render gold solder pads with drill holes (square for Pin 1 / GND)
|
||||
drawSolderPads(g2d, outlineMode, drawingObserver);
|
||||
|
||||
g2d.setComposite(oldComposite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to draw GPIO solder pads (copper/gold pads with drill holes, square for Pin 1).
|
||||
*/
|
||||
protected void drawSolderPads(Graphics2D g2d, boolean outlineMode, IDrawingObserver drawingObserver) {
|
||||
drawPcbSolderPads(g2d, 0, controlPoints.length, true, outlineMode, drawingObserver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
g2d.setColor(TEENSY_GREEN);
|
||||
g2d.fill(new RoundRectangle2D.Double(5, 2, width - 10, height - 4, 3, 3));
|
||||
g2d.setColor(TEENSY_GREEN.darker());
|
||||
g2d.draw(new RoundRectangle2D.Double(5, 2, width - 10, height - 4, 3, 3));
|
||||
|
||||
// GPIO Solder Pads in icon
|
||||
g2d.setColor(PAD_COLOR);
|
||||
for (int y = 5; y <= height - 6; y += 3) {
|
||||
g2d.fillRect(6, y, 2, 2);
|
||||
g2d.fillRect(width - 8, y, 2, 2);
|
||||
}
|
||||
|
||||
// MCU chip
|
||||
g2d.setColor(IC_BODY_COLOR);
|
||||
g2d.fillRect(10, 8, 12, 8);
|
||||
|
||||
// USB connector at top
|
||||
g2d.setColor(USB_METAL_COLOR);
|
||||
g2d.fillRect(12, 2, 8, 3);
|
||||
|
||||
g2d.setColor(Color.WHITE);
|
||||
g2d.setFont(new Font("SansSerif", Font.BOLD, 5));
|
||||
StringUtils.drawCenteredText(g2d, "TEENSY", width / 2, height / 2 + 8,
|
||||
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package org.diylc.components.micro;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TeensyTest {
|
||||
|
||||
@Test
|
||||
public void testControlPointCountAndNames40() {
|
||||
Teensy teensy = new Teensy();
|
||||
Assert.assertEquals(Teensy.TeensyVersion.Teensy_4_0, teensy.getVersion());
|
||||
// 14 left + 14 right + 5 end + 1 VUSB = 34 pins
|
||||
Assert.assertEquals(34, teensy.getControlPointCount());
|
||||
|
||||
for (int i = 0; i < teensy.getControlPointCount(); i++) {
|
||||
String name = teensy.getControlPointNodeName(i);
|
||||
Assert.assertNotNull("Pin " + i + " name should not be null", name);
|
||||
Assert.assertFalse("Pin " + i + " name should not be empty", name.trim().isEmpty());
|
||||
}
|
||||
|
||||
// Left row (0..13, top to bottom)
|
||||
Assert.assertEquals("GND", teensy.getControlPointNodeName(0));
|
||||
Assert.assertEquals("0 (RX1/CS1/CRX2)", teensy.getControlPointNodeName(1));
|
||||
Assert.assertEquals("1 (TX1/MISO1/CTX2)", teensy.getControlPointNodeName(2));
|
||||
Assert.assertEquals("12 (MISO/MQSL)", teensy.getControlPointNodeName(13));
|
||||
|
||||
// Right row (14..27, top to bottom)
|
||||
Assert.assertEquals("VIN (3.6-5.5V)", teensy.getControlPointNodeName(14));
|
||||
Assert.assertEquals("GND", teensy.getControlPointNodeName(15));
|
||||
Assert.assertEquals("3.3V (250mA)", teensy.getControlPointNodeName(16));
|
||||
Assert.assertEquals("23 (A9/CRX1)", teensy.getControlPointNodeName(17));
|
||||
Assert.assertEquals("13 (SCK/CRX1/LED)", teensy.getControlPointNodeName(27));
|
||||
|
||||
// End cluster (28..32)
|
||||
Assert.assertEquals("VBAT", teensy.getControlPointNodeName(28));
|
||||
Assert.assertEquals("3.3V (End)", teensy.getControlPointNodeName(29));
|
||||
Assert.assertEquals("GND (End)", teensy.getControlPointNodeName(30));
|
||||
Assert.assertEquals("Program", teensy.getControlPointNodeName(31));
|
||||
Assert.assertEquals("On/Off", teensy.getControlPointNodeName(32));
|
||||
|
||||
// VUSB (33)
|
||||
Assert.assertEquals("VUSB", teensy.getControlPointNodeName(33));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControlPointCountAndNames41() {
|
||||
Teensy teensy = new Teensy();
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
Assert.assertEquals(Teensy.TeensyVersion.Teensy_4_1, teensy.getVersion());
|
||||
// 24 left + 24 right + 5 mid + 6 eth + 5 usb + 1 VUSB = 65 pins
|
||||
Assert.assertEquals(65, teensy.getControlPointCount());
|
||||
|
||||
for (int i = 0; i < teensy.getControlPointCount(); i++) {
|
||||
String name = teensy.getControlPointNodeName(i);
|
||||
Assert.assertNotNull("Pin " + i + " name should not be null", name);
|
||||
Assert.assertFalse("Pin " + i + " name should not be empty", name.trim().isEmpty());
|
||||
}
|
||||
|
||||
// Left row (0..23, top to bottom)
|
||||
Assert.assertEquals("GND", teensy.getControlPointNodeName(0));
|
||||
Assert.assertEquals("0 (RX1/CS1/CRX2)", teensy.getControlPointNodeName(1));
|
||||
Assert.assertEquals("3.3V", teensy.getControlPointNodeName(14));
|
||||
Assert.assertEquals("32 (OUT1B)", teensy.getControlPointNodeName(23));
|
||||
|
||||
// Right row (24..47, top to bottom)
|
||||
Assert.assertEquals("VIN (3.6-5.5V)", teensy.getControlPointNodeName(24));
|
||||
Assert.assertEquals("GND", teensy.getControlPointNodeName(25));
|
||||
Assert.assertEquals("13 (SCK/LED)", teensy.getControlPointNodeName(37));
|
||||
Assert.assertEquals("33 (MCLK2)", teensy.getControlPointNodeName(47));
|
||||
|
||||
// Middle cluster (48..52)
|
||||
Assert.assertEquals("VBAT", teensy.getControlPointNodeName(48));
|
||||
Assert.assertEquals("3.3V (Mid)", teensy.getControlPointNodeName(49));
|
||||
Assert.assertEquals("GND (Mid)", teensy.getControlPointNodeName(50));
|
||||
Assert.assertEquals("Program", teensy.getControlPointNodeName(51));
|
||||
Assert.assertEquals("On/Off", teensy.getControlPointNodeName(52));
|
||||
|
||||
// Ethernet Header (53..58)
|
||||
Assert.assertEquals("ETH_Rx+", teensy.getControlPointNodeName(53));
|
||||
Assert.assertEquals("ETH_Tx+", teensy.getControlPointNodeName(58));
|
||||
|
||||
// USB Host Header (59..63)
|
||||
Assert.assertEquals("USB_5V", teensy.getControlPointNodeName(59));
|
||||
Assert.assertEquals("USB_GND2", teensy.getControlPointNodeName(63));
|
||||
|
||||
// VUSB (64)
|
||||
Assert.assertEquals("VUSB", teensy.getControlPointNodeName(64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPinGeometryAndSpacing40() {
|
||||
Teensy teensy = new Teensy();
|
||||
double spacing = new Size(0.1d, SizeUnit.in).convertToPixels();
|
||||
|
||||
// Left row pitch (0.1" per pin, 14 pins)
|
||||
for (int i = 0; i < 13; i++) {
|
||||
Point2D p1 = teensy.getControlPoint(i);
|
||||
Point2D p2 = teensy.getControlPoint(i + 1);
|
||||
Assert.assertEquals("Left pin " + i + " Y spacing", spacing, p2.getY() - p1.getY(), 0.01);
|
||||
Assert.assertEquals("Left pin " + i + " X alignment", p1.getX(), p2.getX(), 0.01);
|
||||
}
|
||||
|
||||
// Right row pitch (14 pins)
|
||||
for (int i = 14; i < 27; i++) {
|
||||
Point2D p1 = teensy.getControlPoint(i);
|
||||
Point2D p2 = teensy.getControlPoint(i + 1);
|
||||
Assert.assertEquals("Right pin " + i + " Y spacing", spacing, p2.getY() - p1.getY(), 0.01);
|
||||
Assert.assertEquals("Right pin " + i + " X alignment", p1.getX(), p2.getX(), 0.01);
|
||||
}
|
||||
|
||||
// Row spacing: 0.60" (15.24 mm)
|
||||
double rowSpacing = new Size(15.24d, SizeUnit.mm).convertToPixels();
|
||||
Point2D pLeft = teensy.getControlPoint(0);
|
||||
Point2D pRight = teensy.getControlPoint(14);
|
||||
Assert.assertEquals("Row spacing", rowSpacing, pRight.getX() - pLeft.getX(), 0.01);
|
||||
Assert.assertEquals("Row Y alignment", pLeft.getY(), pRight.getY(), 0.01);
|
||||
|
||||
// End row horizontal spacing: 0.1" pitch at Y = 13 * spacing
|
||||
for (int i = 28; i < 32; i++) {
|
||||
Point2D p1 = teensy.getControlPoint(i);
|
||||
Point2D p2 = teensy.getControlPoint(i + 1);
|
||||
Assert.assertEquals("End pin " + i + " X spacing", spacing, p2.getX() - p1.getX(), 0.01);
|
||||
Assert.assertEquals("End pin " + i + " Y alignment", p1.getY(), p2.getY(), 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPinGeometryAndSpacing41() {
|
||||
Teensy teensy = new Teensy();
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
double spacing = new Size(0.1d, SizeUnit.in).convertToPixels();
|
||||
|
||||
// Left row pitch (0.1" per pin, 24 pins)
|
||||
for (int i = 0; i < 23; i++) {
|
||||
Point2D p1 = teensy.getControlPoint(i);
|
||||
Point2D p2 = teensy.getControlPoint(i + 1);
|
||||
Assert.assertEquals("Left pin " + i + " Y spacing", spacing, p2.getY() - p1.getY(), 0.01);
|
||||
Assert.assertEquals("Left pin " + i + " X alignment", p1.getX(), p2.getX(), 0.01);
|
||||
}
|
||||
|
||||
// Right row pitch (24 pins)
|
||||
for (int i = 24; i < 47; i++) {
|
||||
Point2D p1 = teensy.getControlPoint(i);
|
||||
Point2D p2 = teensy.getControlPoint(i + 1);
|
||||
Assert.assertEquals("Right pin " + i + " Y spacing", spacing, p2.getY() - p1.getY(), 0.01);
|
||||
Assert.assertEquals("Right pin " + i + " X alignment", p1.getX(), p2.getX(), 0.01);
|
||||
}
|
||||
|
||||
// Row spacing: 0.60" (15.24 mm)
|
||||
double rowSpacing = new Size(15.24d, SizeUnit.mm).convertToPixels();
|
||||
Point2D pLeft = teensy.getControlPoint(0);
|
||||
Point2D pRight = teensy.getControlPoint(24);
|
||||
Assert.assertEquals("Row spacing", rowSpacing, pRight.getX() - pLeft.getX(), 0.01);
|
||||
Assert.assertEquals("Row Y alignment", pLeft.getY(), pRight.getY(), 0.01);
|
||||
|
||||
// Middle cluster: horizontal at 0.1" pitch
|
||||
for (int i = 48; i < 52; i++) {
|
||||
Point2D p1 = teensy.getControlPoint(i);
|
||||
Point2D p2 = teensy.getControlPoint(i + 1);
|
||||
Assert.assertEquals("Middle pin " + i + " X spacing", spacing, p2.getX() - p1.getX(), 0.01);
|
||||
Assert.assertEquals("Middle pin " + i + " Y alignment", p1.getY(), p2.getY(), 0.01);
|
||||
}
|
||||
|
||||
// USB Host Header: vertical at 0.1" pitch
|
||||
for (int i = 59; i < 63; i++) {
|
||||
Point2D p1 = teensy.getControlPoint(i);
|
||||
Point2D p2 = teensy.getControlPoint(i + 1);
|
||||
Assert.assertEquals("USB pin " + i + " Y spacing", spacing, p2.getY() - p1.getY(), 0.01);
|
||||
Assert.assertEquals("USB pin " + i + " X alignment", p1.getX(), p2.getX(), 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBodyShapeDimensions40() {
|
||||
Teensy teensy = new Teensy();
|
||||
|
||||
Shape body = teensy.getBodyShape();
|
||||
Assert.assertNotNull(body);
|
||||
Rectangle2D bounds = body.getBounds2D();
|
||||
|
||||
double expectedWidth = new Size(17.78d, SizeUnit.mm).convertToPixels();
|
||||
double expectedHeight = new Size(35.56d, SizeUnit.mm).convertToPixels();
|
||||
|
||||
Assert.assertEquals(expectedWidth, bounds.getWidth(), 0.1);
|
||||
Assert.assertEquals(expectedHeight, bounds.getHeight(), 0.1);
|
||||
|
||||
// Pin 1 offset should be 1.27 mm (0.05") from left and top edges
|
||||
Point2D p0 = teensy.getControlPoint(0);
|
||||
double expectedOffsetX = new Size(1.27d, SizeUnit.mm).convertToPixels();
|
||||
double expectedOffsetY = new Size(1.27d, SizeUnit.mm).convertToPixels();
|
||||
Assert.assertEquals("Pin 1 X offset from left edge", expectedOffsetX, p0.getX() - bounds.getX(), 0.1);
|
||||
Assert.assertEquals("Pin 1 Y offset from top edge", expectedOffsetY, p0.getY() - bounds.getY(), 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBodyShapeDimensions41() {
|
||||
Teensy teensy = new Teensy();
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
|
||||
Shape body = teensy.getBodyShape();
|
||||
Assert.assertNotNull(body);
|
||||
Rectangle2D bounds = body.getBounds2D();
|
||||
|
||||
double expectedWidth = new Size(17.78d, SizeUnit.mm).convertToPixels();
|
||||
double expectedHeight = new Size(60.96d, SizeUnit.mm).convertToPixels();
|
||||
|
||||
Assert.assertEquals(expectedWidth, bounds.getWidth(), 0.1);
|
||||
Assert.assertEquals(expectedHeight, bounds.getHeight(), 0.1);
|
||||
|
||||
// Pin 1 offset should be 1.27 mm (0.05") from left and top edges
|
||||
Point2D p0 = teensy.getControlPoint(0);
|
||||
double expectedOffsetX = new Size(1.27d, SizeUnit.mm).convertToPixels();
|
||||
double expectedOffsetY = new Size(1.27d, SizeUnit.mm).convertToPixels();
|
||||
Assert.assertEquals("Pin 1 X offset from left edge", expectedOffsetX, p0.getX() - bounds.getX(), 0.1);
|
||||
Assert.assertEquals("Pin 1 Y offset from top edge", expectedOffsetY, p0.getY() - bounds.getY(), 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllPinsWithinBoardBounds() {
|
||||
// Teensy 4.0: all 34 pins must be within board bounds
|
||||
Teensy teensy40 = new Teensy();
|
||||
Shape body40 = teensy40.getBodyShape();
|
||||
Rectangle2D bounds40 = body40.getBounds2D();
|
||||
for (int i = 0; i < teensy40.getControlPointCount(); i++) {
|
||||
Point2D p = teensy40.getControlPoint(i);
|
||||
Assert.assertTrue("T4.0 Pin " + i + " (" + teensy40.getControlPointNodeName(i) +
|
||||
") X=" + p.getX() + " should be within board X bounds [" + bounds40.getMinX() + ", " + bounds40.getMaxX() + "]",
|
||||
p.getX() >= bounds40.getMinX() - 0.1 && p.getX() <= bounds40.getMaxX() + 0.1);
|
||||
Assert.assertTrue("T4.0 Pin " + i + " (" + teensy40.getControlPointNodeName(i) +
|
||||
") Y=" + p.getY() + " should be within board Y bounds [" + bounds40.getMinY() + ", " + bounds40.getMaxY() + "]",
|
||||
p.getY() >= bounds40.getMinY() - 0.1 && p.getY() <= bounds40.getMaxY() + 0.1);
|
||||
}
|
||||
|
||||
// Teensy 4.1: all 65 pins must be within board bounds
|
||||
Teensy teensy41 = new Teensy();
|
||||
teensy41.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
Shape body41 = teensy41.getBodyShape();
|
||||
Rectangle2D bounds41 = body41.getBounds2D();
|
||||
for (int i = 0; i < teensy41.getControlPointCount(); i++) {
|
||||
Point2D p = teensy41.getControlPoint(i);
|
||||
Assert.assertTrue("T4.1 Pin " + i + " (" + teensy41.getControlPointNodeName(i) +
|
||||
") X=" + p.getX() + " should be within board X bounds [" + bounds41.getMinX() + ", " + bounds41.getMaxX() + "]",
|
||||
p.getX() >= bounds41.getMinX() - 0.1 && p.getX() <= bounds41.getMaxX() + 0.1);
|
||||
Assert.assertTrue("T4.1 Pin " + i + " (" + teensy41.getControlPointNodeName(i) +
|
||||
") Y=" + p.getY() + " should be within board Y bounds [" + bounds41.getMinY() + ", " + bounds41.getMaxY() + "]",
|
||||
p.getY() >= bounds41.getMinY() - 0.1 && p.getY() <= bounds41.getMaxY() + 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionSwitching() {
|
||||
Teensy teensy = new Teensy();
|
||||
Assert.assertEquals(34, teensy.getControlPointCount());
|
||||
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
Assert.assertEquals(65, teensy.getControlPointCount());
|
||||
Assert.assertEquals("VUSB", teensy.getControlPointNodeName(64));
|
||||
Assert.assertEquals("GND", teensy.getControlPointNodeName(0));
|
||||
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_0);
|
||||
Assert.assertEquals(34, teensy.getControlPointCount());
|
||||
Assert.assertEquals("VUSB", teensy.getControlPointNodeName(33));
|
||||
Assert.assertEquals("GND", teensy.getControlPointNodeName(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDrawingAndOutline() {
|
||||
Teensy teensy = new Teensy();
|
||||
teensy.setControlPoint(new Point2D.Double(200, 200), 0);
|
||||
|
||||
BufferedImage img = new BufferedImage(600, 600, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = img.createGraphics();
|
||||
g2d.setClip(new Rectangle(0, 0, 600, 600));
|
||||
|
||||
Project project = new Project();
|
||||
final AtomicInteger trackingStartCount = new AtomicInteger(0);
|
||||
final AtomicInteger trackingStopCount = new AtomicInteger(0);
|
||||
|
||||
IDrawingObserver observer = new IDrawingObserver() {
|
||||
@Override public void startTracking() {
|
||||
trackingStartCount.incrementAndGet();
|
||||
}
|
||||
@Override public void stopTracking() {
|
||||
trackingStopCount.incrementAndGet();
|
||||
}
|
||||
@Override public void startTrackingContinuityArea(boolean positive) {}
|
||||
@Override public void stopTrackingContinuityArea() {}
|
||||
@Override public boolean isTrackingContinuityArea() { return false; }
|
||||
@Override public void setContinuityMarker(String marker) {}
|
||||
};
|
||||
|
||||
// Test normal draw
|
||||
teensy.draw(g2d, ComponentState.NORMAL, false, project, observer);
|
||||
Assert.assertEquals(1, trackingStartCount.get());
|
||||
Assert.assertEquals(1, trackingStopCount.get());
|
||||
|
||||
// Test selected state and outline mode
|
||||
teensy.draw(g2d, ComponentState.SELECTED, false, project, observer);
|
||||
teensy.draw(g2d, ComponentState.NORMAL, true, project, observer);
|
||||
|
||||
// Test icon drawing
|
||||
teensy.drawIcon(g2d, 32, 32);
|
||||
|
||||
// Test Teensy 4.1 draw
|
||||
teensy.setVersion(Teensy.TeensyVersion.Teensy_4_1);
|
||||
teensy.draw(g2d, ComponentState.NORMAL, false, project, observer);
|
||||
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRotation() {
|
||||
Teensy teensy = new Teensy();
|
||||
teensy.setControlPoint(new Point2D.Double(200, 200), 0);
|
||||
|
||||
Point2D p0Initial = teensy.getControlPoint(0);
|
||||
Point2D p14Initial = teensy.getControlPoint(14); // First right-row pin
|
||||
|
||||
teensy.setOrientation(Orientation._90);
|
||||
Point2D p0Rot = teensy.getControlPoint(0);
|
||||
Point2D p14Rot = teensy.getControlPoint(14);
|
||||
|
||||
// p0 should stay in place
|
||||
Assert.assertEquals(p0Initial.getX(), p0Rot.getX(), 0.01);
|
||||
Assert.assertEquals(p0Initial.getY(), p0Rot.getY(), 0.01);
|
||||
|
||||
// After 90 deg clockwise rotation, right row pin should move from (x + rowSpacing, y) to (x, y + rowSpacing)
|
||||
double rowSpacing = new Size(15.24d, SizeUnit.mm).convertToPixels();
|
||||
Assert.assertEquals(p0Rot.getX(), p14Rot.getX(), 0.01);
|
||||
Assert.assertEquals(p0Rot.getY() + rowSpacing, p14Rot.getY(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBoardColor() {
|
||||
Teensy teensy = new Teensy();
|
||||
// Board should be green (like Pi Zero)
|
||||
Assert.assertEquals(Teensy.TEENSY_GREEN, teensy.getBodyColor());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user