From bfacf821f448c4d99055738e90de81e50dcb156c Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Fri, 25 Apr 2025 14:39:25 +1000 Subject: [PATCH] rp2/boards/WEACTSTUDIO_RP2350B_CORE: Add board.pinout. This adds an ANSI-rendered pinout for the WeAct Studio RP2350B Core board. Signed-off-by: Matt Trentini --- .../WEACTSTUDIO_RP2350B_CORE/manifest.py | 1 + .../WEACTSTUDIO_RP2350B_CORE/modules/board.py | 8 ++ tools/make_pinout_diagram/README.md | 43 ++++++++ tools/make_pinout_diagram/compress.py | 10 ++ tools/make_pinout_diagram/pinout.py | 104 ++++++++++++++++++ tools/make_pinout_diagram/weact_pinout.png | Bin 0 -> 22295 bytes 6 files changed, 166 insertions(+) create mode 100644 ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py create mode 100644 tools/make_pinout_diagram/README.md create mode 100644 tools/make_pinout_diagram/compress.py create mode 100644 tools/make_pinout_diagram/pinout.py create mode 100644 tools/make_pinout_diagram/weact_pinout.png diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py index 832942f052..7ae2ed15d9 100644 --- a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/manifest.py @@ -1 +1,2 @@ include("$(PORT_DIR)/boards/manifest.py") +freeze("modules") diff --git a/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py new file mode 100644 index 0000000000..8e3cc2953d --- /dev/null +++ b/ports/rp2/boards/WEACTSTUDIO_RP2350B_CORE/modules/board.py @@ -0,0 +1,8 @@ +import deflate +import io + +_compressed_pinout = b'(\x91\xcd\x92\xbdn\xdb0\x10\xc7w\xbdB\x96\x1b\x93M$E\xc9\x82\xa7:Q\x83"A\x1c\xd8\x89;d*\x1a\x0f\x1d\x04\x03A:t\x0b\xbc6F:HA\x91%C\xbb\x14h\x9d\x89amo\xa7\xaf\xde_\xc3\xf8\xfa\xe3\xe5\x87\x19\x8cN)\xe3\xee\x00\xf6gWS\x18\xcc\xde]]\xfe\r\xdc\xb9pc\xc7\x01\x83\x89\xf4\x97Hn\xe4\x82Q4\x8e\xceVqG\xd3O\xb0{(\xab\xeeA\xee\xceV\xba\x94\xf1\xc9|3i0\x1c\x9eUk\xaf\xbd\xffp\xa5KG\x82\xb1^\x9f\xf7\t\x8f\xa58\xd9\x8ey1P?\'\x06\x83\x9b\xaf\xdc\x12\xa4\x88\xa0\xd4\x8d\xc5\xedou\xac9*p\xd2e}\xb3\x81[\xbb\x8d\x12?\x86\xfd\xe3\xa3\xe2:\xd0\x95\xae\x1f)y\xf5/\xf7P0\xa0\xach\xb5!\xbd\x87\xeb\x12\xb4\xd7%\x99\x8b\x9f\xdf\xb3om\x93\xeb"\xff\x986\x07o\x86\xe5\x17uV\x866(CPe\x98\x8b+\x13v\x98\x98g5\x1fO\xf5M\xed\x8aL\tq\xf7\xa0\xae\xc8&J\xa9fV\x8e\xc8\xf7N\x98\x8a\xb6U\r\xc7\x06\x12\xe2\xaa\xe1\xa23\xd2Z5i\xc7\xd1\xc1\xba\x1c\rcq\xffc\xe5\xd9\xae\xc4\xd6\x17Y\xcf\x1c\xe95\xa8\x17\xe0\xeay\xb8z\xac\x83z\xbb\x87\xa7\x94\xef\xc9\x8dx\xfc,\x1e\x17\xb0a\xb6\xb0~\x03,\xc7a\xf1t\xc6\xbb\x8cJn\x125\xff-^\x0e\x16o\r\x10\x86\xc3\xe2\x83\xc1\x82\xae\xb0\x05j\x05\xd7\x16\x966\xc0\x12#,\xed\xc5\xe0\xb98l\xd8\x1dv\xa1\xe1\xb6\x85\xc5_\x0b\x10\xe2\xb0f\xad\x94\x9b\xd8\x8c\xf1\xa2\x82j\r\x0b\xf8\x1c\x02\x048\xac\x87\xc3\xb2\xee\xb0\n\xb7\x84j\x0f\xeb7\xc0r\x1c\xd6\x9c\xae\xdc\xdc\x06\xb6l\xb6\xa0\xe6\xb6\xe4\xa0l\x05\n\xa3\xf3\x130\x01\x05\xff\x0f\x10m\x00"\xc6\xce\x91\x18\xd8\x84\x99\xb2K\xde\xd6\x98"\xb9\x15\xc9Mm}y1l\x17\x9d\xb8\xc9(zm\xc4\xf63\xa0\xbb\x87j\x01\x7f]\x80\xca\x0b\xa2\x13C\x8b\xef\xbf5\xb3\xcf+\x9b\n\xaf\xa6D~\xa4\xe75=X\x13\xd0\x96\xd9\x18\xd0\x0c]\x1cm\r\xdd\xb2\xc9r\xfc\xf8\xc4\xfc\\\xd9\xe2\xc1\xf9\xb8\xc0-\x9bH\x9fu\xa3W_\x89\xee\xf0\xebV\xb9\xe9\x93\xe3\xfc\x01\x83\'\xd6K' + + +def pinout(): + print(str(deflate.DeflateIO(io.BytesIO(_compressed_pinout), deflate.ZLIB).read(), "utf-8")) diff --git a/tools/make_pinout_diagram/README.md b/tools/make_pinout_diagram/README.md new file mode 100644 index 0000000000..bebd7df7c8 --- /dev/null +++ b/tools/make_pinout_diagram/README.md @@ -0,0 +1,43 @@ +# Pinout diagram + +Pinout diagrams can be a helpful tool to display the pin layout for a board. + +Rendering them using ANSI and building them into a board's firmware means they +are always conveniently available, even over a serial connection. + +![WeAct RP2350B Core pinout diagram](weact_pinout.png) + +## Overview + +`pinout.py` generates a unicode pinout diagram that uses ANSI escape characters +to add colour. It currently generates the pinout diagram for the WeActStudio +RP2350B board but is an example that could be extended for any board. + +Display the output by executing the script: + +```bash +python pinout.py +``` + + +## Compression + +`compress.py` uses zlib to _compress input_ and output a _byte string_. + +The output from `pinout.py` can be large but compresses efficiently, so the +intent is that the byte string output from `compress.py` can then be copied to +`../modules/board.py` so that the _compressed_ pinout will be included in the +firmware. + +To execute: + +```bash +python pinout.py | python compress.py +``` + +## Reference + +[Build your own Command Line with ANSI escape +codes](https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html) +provides a good reference of how to use ANSI codes, including helpful colour +lookups. diff --git a/tools/make_pinout_diagram/compress.py b/tools/make_pinout_diagram/compress.py new file mode 100644 index 0000000000..7e95ab0352 --- /dev/null +++ b/tools/make_pinout_diagram/compress.py @@ -0,0 +1,10 @@ +import sys +import zlib + +data_str = sys.stdin.read() +compressed = zlib.compress(bytes(data_str, "utf-8"), wbits=10) +print( + f"{len(data_str) / len(compressed):.1f}x smaller: uncompressed={len(data_str)}, compressed={len(compressed)}", + file=sys.stderr, +) +print(compressed) diff --git a/tools/make_pinout_diagram/pinout.py b/tools/make_pinout_diagram/pinout.py new file mode 100644 index 0000000000..569f40510c --- /dev/null +++ b/tools/make_pinout_diagram/pinout.py @@ -0,0 +1,104 @@ +import re + +pinout_str = """ + \u001b[44;1m WeAct Studio RP2350B Core Board \u001b[0m + + ╭── RESET + Key (GP23) ───╮ │ ╭── BOOT + ╭────────────────────────────────╮ + [26 ] [25 ] │⌾ ⌾ ╭─╮╭─╮╭─╮ ╭─[CLK] ⌾ ⌾│ [24 ] [ 23 ] + [28 ] [27 ] │⌾ ⌾ │⬤││⬤││⬤│ │ ╭─[DIO] ⌾ ⌾│ [22 ] [ 21 ] + [30 ] [29 ] │⌾ ⌾ ╰─╯╰─╯╰─╯[⏚]╮ │ │ ╭─[3V3]⌾ ⌾│ [20 ] [ 19 ] + [32 ] [31 ] │⌾ ⌾ LED ▩ ⌾ ⌾ ⌾ ⌾ ⌾ ⌾│ [18 ] [ 17 ] + [34 ] [33 ] │⌾ ⌾ (GP25) ⟋⟍ ⌾ ⌾│ [16 ] [ 15 ] + [36 ] [35 ] │⌾ ⌾ ⟋ ⟍ ⌾ ⌾│ [14 ] [ 13 ] + [38 ] [37 ] │⌾ ⌾ ⟋ ⟍ ⌾ ⌾│ [12 ] [ 11 ] + [40 ] [39 ] │⌾ ⌾ ⟍ ⟋ ⌾ ⌾│ [10 ] [ 9 ] + [42 ] [41 ] │⌾ ⌾ ⟍ ⟋ ⌾ ⌾│ [ 8 ] [ 7 ] + [44 ] [43 ] │⌾ ⌾ ⟍⟋ ⌾ ⌾│ [ 6 ] [ 5 ] + [46 ] [45 ] │⌾ ⌾ ⌾ ⌾│ [ 4 ] [ 3 ] + [RUN] [47 ] │⌾ ⌾ ⌾ ⌾│ [ 2 ] [ 1 ] + [3V3] [3V3] │⌾ ⌾ ┌──────┐ ⌾ ⌾│ [ 0 ] [VREF] + [ ⏚ ] [EN ] │▣ ⌾ │ │ ▣ ▣│ [ ⏚ ] [ ⏚ ] + [ ⏚ ] [ ⏚ ] │▣ ▣ │ │ ⌾ ⌾│ [5V ] [VBUS] + ╰────────────└──────┘────────────╯ +""" + + +def ansi_colour(wrap_str: str, colours: tuple[int | None, int | None]) -> str: + """Wrap a string in the ANSI foreground and background colour escape sequences.""" + wrapped_str = "" + + wrapped_str += "\u001b[38;5;" + str(colours[0]) + "m" if colours[0] else "" + wrapped_str += "\u001b[48;5;" + str(colours[1]) + "m" if colours[1] else "" + wrapped_str += wrap_str + wrapped_str += "\u001b[0m" if colours[0] or colours[1] else "" + + return wrapped_str + + +def add_colour(pinout_str): + symbol_colours = { + "⌾": (220, None), # Pin (Yellow) + "▣": (220, None), # Ground pin (Yellow) + "↺": (15, None), # Reset (White) + "▩": (129, None), # LED (Purple) + } + + for symbol, colours in symbol_colours.items(): + pinout_str = pinout_str.replace(symbol, ansi_colour(symbol, colours)) + return pinout_str + + +def colour_tags(matchobj: re.Match) -> str: + white_on_red = (15, 1) + white_on_peach = (15, 216) + white_on_dark_green = (15, 28) + white_on_black = (15, 16) + black_on_pink = (16, 224) + + tag_colours = { + "5V": white_on_red, + "VBUS": white_on_red, + "VREF": white_on_dark_green, + "3V3": white_on_red, + "⏚": white_on_black, + "EN": black_on_pink, + "CLK": white_on_peach, + "DIO": white_on_peach, + } + + if matchobj.group(2) not in tag_colours.keys(): + return matchobj.group(0) + + pin_colours = tag_colours[matchobj.group(2)] + + return ansi_colour(matchobj.group(1), pin_colours) + + +def replace_tags(pinout_str): + return re.sub(r"(\[\s*(\S+)\s*\])", colour_tags, pinout_str) + + +def colour_pins(matchobj: re.Match) -> str: + # Regular GPIO is green, ADC pins (40-47) are darker greeen + gpio_colours = (15, 34) # White on green + adc_colours = (15, 28) # White on dark green + pin_number = int(matchobj.group(2)) + + pin_colours = gpio_colours if pin_number < 40 else adc_colours + + return ansi_colour(matchobj.group(1), pin_colours) + + +def replace_gpio(pinout_str) -> str: + return re.sub(r"(\[\s*(\d+)\s*\])", colour_pins, pinout_str) + + +pinout_str = replace_gpio(replace_tags(add_colour(pinout_str))) +# Colours include a square bracket; temporarily change them! +pinout_str = pinout_str.replace("\u001b[", "~") +pinout_str = pinout_str.replace("[", " ").replace("]", " ") +pinout_str = pinout_str.replace("~", "\u001b[") # Put them back + +print(pinout_str) diff --git a/tools/make_pinout_diagram/weact_pinout.png b/tools/make_pinout_diagram/weact_pinout.png new file mode 100644 index 0000000000000000000000000000000000000000..35240ed9e16d09d51c09ea3d4424cf8a25a1508d GIT binary patch literal 22295 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 literal 0 HcmV?d00001