diff --git a/docs/assets/apollo-dev-1-pinout-1.png b/docs/assets/apollo-dev-1-pinout-1.png deleted file mode 100755 index 249c2e3be9..0000000000 Binary files a/docs/assets/apollo-dev-1-pinout-1.png and /dev/null differ diff --git a/docs/assets/apollo-dev-1-pinout.png b/docs/assets/apollo-dev-1-pinout.png deleted file mode 100755 index 442ca59b06..0000000000 Binary files a/docs/assets/apollo-dev-1-pinout.png and /dev/null differ diff --git a/docs/assets/apollo-dev-1-pinout.webp b/docs/assets/apollo-dev-1-pinout.webp new file mode 100644 index 0000000000..d32953b6e7 Binary files /dev/null and b/docs/assets/apollo-dev-1-pinout.webp differ diff --git a/docs/assets/apollo-dev-2-pinout-1.png b/docs/assets/apollo-dev-2-pinout-1.png deleted file mode 100755 index c045224f5f..0000000000 Binary files a/docs/assets/apollo-dev-2-pinout-1.png and /dev/null differ diff --git a/docs/assets/apollo-dev-2-pinout.png b/docs/assets/apollo-dev-2-pinout.png deleted file mode 100755 index 727f35f12f..0000000000 Binary files a/docs/assets/apollo-dev-2-pinout.png and /dev/null differ diff --git a/docs/assets/apollo-dev-2-pinout.webp b/docs/assets/apollo-dev-2-pinout.webp new file mode 100644 index 0000000000..4b5e5c3020 Binary files /dev/null and b/docs/assets/apollo-dev-2-pinout.webp differ diff --git a/docs/products/dev1/introduction.md b/docs/products/dev1/introduction.md index 7b1fc7208e..b7051bc977 100755 --- a/docs/products/dev1/introduction.md +++ b/docs/products/dev1/introduction.md @@ -2,7 +2,7 @@ title: Apollo DEV-1 Introduction description: Apollo DEV-1 Introduction. --- -![](../../assets/apollo-dev-1-pinout-1.png) +![Apollo DEV-1 pinout showing the GPIO number and functions of every header pin](/assets/apollo-dev-1-pinout.webp) The Apollo DEV-1 is a very small dev board that we use to prototype before creating other new products. It has a built in RGB light (using GPIO3) and can push up to 600mA out of the 3.3v pin however 100-200mA of that will be used by the microcontroller itself. You are able to back-feed power via the 5v and G (ground) pins or use the USB-C port to power it, but NOT both at the same time. @@ -27,15 +27,15 @@ i2c: sda: GPIO1 scl: GPIO0 -RX: 20 -TX: 21 +#These are the UART pins +uart: + tx_pin: GPIO21 + rx_pin: GPIO20 + baud_rate: 115200 #match the device you connect -ADC Pins: 0,1,2,3,4,5 -``` +#ADC pins: GPIO0, GPIO1, GPIO2, GPIO4 -```yaml -#This is the onboard LED pin -GPIO3 +#Onboard RGB LED: GPIO3 ``` **Example ESPHome yaml:** @@ -53,7 +53,7 @@ esphome: comment: Apollo DEV-1 project: - name: "ApolloAutomation.MSR-2" + name: "ApolloAutomation.DEV-1" version: "${version}" # Define Board diff --git a/docs/products/dev2/introduction.md b/docs/products/dev2/introduction.md index f0b60b49cd..51fa371da7 100755 --- a/docs/products/dev2/introduction.md +++ b/docs/products/dev2/introduction.md @@ -2,7 +2,7 @@ title: Apollo DEV-2 Introduction description: Apollo DEV-2 Introduction. --- -![](../../assets/apollo-dev-2-pinout-1.png) +![Apollo DEV-2 pinout showing the GPIO number and functions of every header pin](/assets/apollo-dev-2-pinout.webp) The Apollo DEV-2 is a very small dev board that we use to prototype before creating other new products. It has a built in RGB light (using GPIO3) and can push up to 600mA out of the 3.3v pin however 100-200mA of that will be used by the microcontroller itself. You are able to back-feed power via the 5v and G (ground) pins or use the USB-C port to power it, but NOT both at the same time. @@ -31,15 +31,15 @@ i2c: sda: GPIO1 scl: GPIO0 -RX: 30 -TX: 31 +#These are the UART pins +uart: + tx_pin: GPIO16 + rx_pin: GPIO17 + baud_rate: 115200 #match the device you connect -ADC Pins: 0,1,2,3,4,5 -``` +#ADC pins: GPIO0, GPIO1, GPIO2, GPIO4, GPIO5, GPIO6 -```yaml -#This is the onboard LED pin -GPIO3 +#Onboard RGB LED: GPIO3 ``` **Example ESPHome yaml:** diff --git a/tools/pinouts/README.md b/tools/pinouts/README.md new file mode 100644 index 0000000000..3110980523 --- /dev/null +++ b/tools/pinouts/README.md @@ -0,0 +1,22 @@ +# DEV board pinouts + +Generates the DEV-1 and DEV-2 pinout diagrams used on the wiki: + +- `docs/assets/apollo-dev-1-pinout.webp` +- `docs/assets/apollo-dev-2-pinout.webp` + +The label style follows [boardgen](https://github.com/kuba2k2/boardgen). + +## Fix a label + +1. Edit the pin table in `boards.json`. Each pad is a list of `kind:text` tags, listed from the board outward. The kinds are `power`, `ground`, `gpio`, `adc`, `i2c`, `uart`, `spi` and `led`. +2. Run `python tools/pinouts/make_pinout.py` from the repo root. You can also pass one board, such as `dev-2`. +3. Commit the updated `boards.json` and the regenerated `.webp` files. + +Requirements: Python with [Pillow](https://pypi.org/project/Pillow/), plus Chrome or Edge to render the SVG. + +Pin numbers are GPIO numbers, not module pin numbers. Check any new label against the module datasheet ([ESP32-C3-MINI-1](https://www.espressif.com/sites/default/files/documentation/esp32-c3-mini-1_datasheet_en.pdf), [ESP32-C6-MINI-1](https://www.espressif.com/sites/default/files/documentation/esp32-c6-mini-1_mini-1u_datasheet_en.pdf)). + +## Board art + +`dev1-board.png` and `dev2-board.png` are the EasyEDA 3D renders in `renders/`, cropped to the PCB. Only rerun `python tools/pinouts/prep_renders.py` if a render changes. If the board art moves, the pad positions in `boards.json` (`x`, `y0`, `pitch`) have to be remeasured. diff --git a/tools/pinouts/boards.json b/tools/pinouts/boards.json new file mode 100644 index 0000000000..5d83de1e7b --- /dev/null +++ b/tools/pinouts/boards.json @@ -0,0 +1,195 @@ +{ + "dev-1": { + "title": "Apollo DEV-1", + "chip": "ESP32-C3", + "example": "GPIO21", + "photo": "dev1-board.png", + "left": { + "x": 38, + "y0": 172, + "pitch": 80.8 + }, + "right": { + "x": 700, + "y0": 172, + "pitch": 80.8 + }, + "pins": { + "left": [ + [ + "power:3V3" + ], + [ + "ground:GND" + ], + [ + "gpio:2", + "adc:ADC", + "spi:MISO" + ], + [ + "gpio:4", + "adc:ADC" + ], + [ + "gpio:5" + ], + [ + "gpio:0", + "adc:ADC", + "i2c:SCL" + ], + [ + "gpio:1", + "adc:ADC", + "i2c:SDA" + ] + ], + "right": [ + [ + "power:5V" + ], + [ + "ground:GND" + ], + [ + "gpio:10", + "spi:CS" + ], + [ + "gpio:21", + "uart:TX" + ], + [ + "gpio:20", + "uart:RX" + ], + [ + "gpio:7", + "spi:MOSI" + ], + [ + "gpio:6", + "spi:CLK" + ] + ] + }, + "led": { + "x": 688, + "y": 45, + "tags": [ + "gpio:3", + "led:RGB LED" + ] + } + }, + "dev-2": { + "title": "Apollo DEV-2", + "chip": "ESP32-C6", + "example": "GPIO16", + "photo": "dev2-board.png", + "left": { + "x": 38, + "y0": 145, + "pitch": 81.3 + }, + "right": { + "x": 700, + "y0": 145, + "pitch": 81.3 + }, + "pins": { + "left": [ + [ + "power:3V3" + ], + [ + "ground:GND" + ], + [ + "gpio:2", + "adc:ADC", + "spi:MISO" + ], + [ + "gpio:4", + "adc:ADC" + ], + [ + "gpio:5", + "adc:ADC" + ], + [ + "gpio:0", + "adc:ADC", + "i2c:SCL" + ], + [ + "gpio:1", + "adc:ADC", + "i2c:SDA" + ], + [ + "gpio:6", + "adc:ADC", + "spi:CLK" + ], + [ + "gpio:7", + "spi:MOSI" + ], + [ + "gpio:14" + ], + [ + "ground:GND" + ] + ], + "right": [ + [ + "power:5V" + ], + [ + "ground:GND" + ], + [ + "gpio:16", + "uart:TX" + ], + [ + "gpio:17", + "uart:RX" + ], + [ + "gpio:23" + ], + [ + "gpio:22" + ], + [ + "gpio:21" + ], + [ + "gpio:20" + ], + [ + "gpio:19" + ], + [ + "gpio:18" + ], + [ + "gpio:15" + ] + ] + }, + "led": { + "x": 680, + "y": 45, + "tags": [ + "gpio:3", + "led:RGB LED" + ] + } + } +} diff --git a/tools/pinouts/dev1-board.png b/tools/pinouts/dev1-board.png new file mode 100644 index 0000000000..334d1e04b2 Binary files /dev/null and b/tools/pinouts/dev1-board.png differ diff --git a/tools/pinouts/dev2-board.png b/tools/pinouts/dev2-board.png new file mode 100644 index 0000000000..e06bebfa0d Binary files /dev/null and b/tools/pinouts/dev2-board.png differ diff --git a/tools/pinouts/make_pinout.py b/tools/pinouts/make_pinout.py new file mode 100644 index 0000000000..bf6e1f7b18 --- /dev/null +++ b/tools/pinouts/make_pinout.py @@ -0,0 +1,162 @@ +"""Build DEV board pinout diagrams from boards.json. + +Tags follow boardgen's label style (https://github.com/kuba2k2/boardgen): 15 degree +skewed blocks, Consolas text, dark ink on light fills and white ink on dark ones. + +Usage: python make_pinout.py [board ...] +Renders docs/assets/apollo--pinout.webp with headless Chrome or Edge. Needs Pillow. +""" +import base64 +import colorsys +import json +import math +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path + +from PIL import Image + +HERE = Path(__file__).parent +ASSETS = HERE.parent.parent / "docs" / "assets" + +COLORS = { + "power": "#CD3C24", + "ground": "#000000", + "gpio": "#4A7FC1", + "adc": "#8AD039", + "i2c": "#FF9955", + "uart": "#DCD4EE", + "spi": "#C2629A", + "led": "#C8C8C8", +} +LEGEND = [("power", "Power"), ("ground", "Ground"), ("gpio", "GPIO"), ("adc", "ADC"), + ("i2c", "I2C"), ("uart", "UART"), ("spi", "SPI")] + +TAG_W = 116 +TAG_H = 50 +TAG_GAP = 6 +SKEW = 15 # degrees, same as boardgen +LEAD = 60 # leader line length from board edge +LINE_GAP = 14 # space between leader line and first tag +MARGIN = 30 +MONO = "Consolas, 'DejaVu Sans Mono', monospace" +SANS = "Arial, 'Helvetica Neue', sans-serif" + + +def ink(fill): + r, g, b = (int(fill[i:i + 2], 16) / 255 for i in (1, 3, 5)) + return "#423F42" if colorsys.rgb_to_hls(r, g, b)[1] > 0.5 else "#FFFFFF" + + +def tag_width(text): + return max(TAG_W, 26 + 17 * len(text)) + + +def tag(x, y, kind, text): + fill = COLORS[kind] + w = tag_width(text) + shift = TAG_H * math.tan(math.radians(SKEW)) / 2 + top = y - TAG_H / 2 + return (w, f'' + f'{text}') + + +def row(tags): + return [t.split(":", 1) for t in tags] + + +def row_width(tags): + return sum(tag_width(t) for _, t in row(tags)) + TAG_GAP * (len(tags) - 1) + + +def build(name, b): + photo = HERE / b["photo"] + pw, ph = Image.open(photo).size + data = base64.b64encode(photo.read_bytes()).decode() + + left_w = max(row_width(r) for r in b["pins"]["left"]) + right_w = max(row_width(r) for r in b["pins"]["right"] + [b["led"]["tags"]]) + bx = MARGIN + left_w + LINE_GAP + LEAD + by = MARGIN + width = bx + pw + LEAD + LINE_GAP + right_w + MARGIN + legend_y = by + ph + 55 + height = legend_y + 40 + + lines, tags = [], [] + + def draw_row(side, py, px, entries, dashed=False): + dash = ' stroke-dasharray="10 6"' if dashed else "" + if side == "left": + x = bx - LEAD + lines.append(f'') + x -= LINE_GAP + for kind, text in row(entries): + x -= tag_width(text) + tags.append(tag(x, py, kind, text)[1]) + x -= TAG_GAP + else: + x = bx + pw + LEAD + lines.append(f'') + x += LINE_GAP + for kind, text in row(entries): + w, svg = tag(x, py, kind, text) + tags.append(svg) + x += w + TAG_GAP + + for side in ("left", "right"): + geo = b[side] + for i, entries in enumerate(b["pins"][side]): + draw_row(side, by + geo["y0"] + geo["pitch"] * i, bx + geo["x"], entries) + led = b["led"] + draw_row("right", by + led["y"], bx + led["x"], led["tags"]) + + out = [f'', + '', + *lines, + # board goes over the leader lines so they appear to run out from under each pad + f'', + *tags] + + items = [(kind, label, 30 + 10 + 14 * len(label) + 34) for kind, label in LEGEND] + x = (width - (sum(w for *_, w in items) - 34)) / 2 + for kind, label, w in items: + out.append(f'') + out.append(f'{label}') + x += w + out.append("") + + svg_path = HERE / f"{name}-pinout.svg" + svg_path.write_text("\n".join(out), encoding="utf-8") + return svg_path, int(round(width)), int(round(height)) + + +def render(name, svg_path, w, h): + browser = next((p for p in ( + r"C:\Program Files\Google\Chrome\Application\chrome.exe", + r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", + shutil.which("chromium"), shutil.which("google-chrome"), + ) if p and Path(p).exists()), None) + if not browser: + sys.exit("no Chrome/Edge found to render the SVG") + with tempfile.TemporaryDirectory() as tmp: + png = Path(tmp) / "pinout.png" + subprocess.run([browser, "--headless", "--disable-gpu", "--hide-scrollbars", + f"--window-size={w},{h}", f"--screenshot={png}", svg_path.resolve().as_uri()], + check=True, capture_output=True) + # WebP, not PNG: CI shrinks every PNG to 750px wide, which blurs the labels + webp = ASSETS / f"apollo-{name}-pinout.webp" + Image.open(png).save(webp, quality=90, method=6) + svg_path.unlink() + print("wrote", webp) + + +if __name__ == "__main__": + boards = json.loads((HERE / "boards.json").read_text(encoding="utf-8")) + for name in sys.argv[1:] or boards: + render(name, *build(name, boards[name])) diff --git a/tools/pinouts/prep_renders.py b/tools/pinouts/prep_renders.py new file mode 100644 index 0000000000..de4bbad9c5 --- /dev/null +++ b/tools/pinouts/prep_renders.py @@ -0,0 +1,51 @@ +"""One-time prep: crop the EasyEDA board renders to the PCB and knock out the background. + +Reads renders/apollo-dev-N-image-1.png and writes devN-board.png next to this script, +which make_pinout.py then uses. Only rerun this when a render changes. Needs Pillow. +""" +from pathlib import Path + +from PIL import Image, ImageDraw + +HERE = Path(__file__).parent + +# regions painted over with the module shield's flat white (the EasyEDA logo on DEV-2) +ERASE = {"dev2-board.png": [(398, 438, 526, 488)]} + + +def is_blue(p): + r, g, b = p[:3] + return p[3] > 0 and b > r + 40 and b > g + 20 + + +def prep(src, out): + im = Image.open(src).convert("RGBA") + px = im.load() + w, h = im.size + # per row, everything between the outermost blue pixels is PCB; median-smoothed + # over neighbouring rows so edge pads don't notch the outline + spans = [] + for y in range(h): + blue = [x for x in range(w) if is_blue(px[x, y])] + spans.append((blue[0], blue[-1]) if len(blue) > w // 10 else None) + for y in range(h): + near = [s for s in spans[max(0, y - 40):y + 41] if s] + if spans[y] is None or len(near) < 20: + lo, hi = w, -1 + else: + lo = sorted(s[0] for s in near)[len(near) // 2] - 2 + hi = sorted(s[1] for s in near)[len(near) // 2] + 2 + for x in range(w): + if x < lo or x > hi: + px[x, y] = (0, 0, 0, 0) + im = im.crop(im.getbbox()) + im = im.resize((round(im.width * 1000 / im.height), 1000), Image.LANCZOS) + for box in ERASE.get(out, []): + ImageDraw.Draw(im).rectangle(box, fill=(255, 255, 255, 255)) + im.save(HERE / out) + print("wrote", out, im.size) + + +if __name__ == "__main__": + prep(HERE / "renders" / "apollo-dev-1-image-1.png", "dev1-board.png") + prep(HERE / "renders" / "apollo-dev-2-image-1.png", "dev2-board.png") diff --git a/tools/pinouts/renders/apollo-dev-1-image-1.png b/tools/pinouts/renders/apollo-dev-1-image-1.png new file mode 100644 index 0000000000..e66de70dd3 Binary files /dev/null and b/tools/pinouts/renders/apollo-dev-1-image-1.png differ diff --git a/tools/pinouts/renders/apollo-dev-2-image-1.png b/tools/pinouts/renders/apollo-dev-2-image-1.png new file mode 100644 index 0000000000..a7ce80caf9 Binary files /dev/null and b/tools/pinouts/renders/apollo-dev-2-image-1.png differ