The Arcology Garden

The Monticello Drive Revision One

Contents

I want to build a portable version of this with bluetooth, battery, and low-profile switches.

Ergonomic, self-fitted, just the right size to fit on my Framework Laptop with little stand-offs so that it can rest on the clamshell without hitting any keys, just the right size to fit in to a laptop travel bag. i like the idea of keeping a folding split design like the rev0 has but made well enough to travel safely, but if I could build something less than 5mm thick it doesn't matter quite so much, does it?

Here's a low-profile choc PCB I could have printed, and it might even work:

Rev 1.1 Photos

2025-07-07_22-06-27_de3d95802423086e14a067660060d63485575a43ab54626fdfd8be096e5db04d.jpg

2025-07-07_22-06-38_ccee75317123aa619988ca1a4c974493bd2c91893d426c28d6704213cc4fd4b8.jpg

2025-07-07_22-06-55_456d68c9e24d3cfebb763b2dd534c8b80aa2a9cc48d10d3abd6fcd16464bd9c0.jpg

Rev 1.0 Photos

data/202405/15T195735.970398/2024-05-15_19-59-04_ea2f254dcd6dfc1bc7f6f7fb135b14ff0a901899dd9beb1ce42ff2c9f699f208.png

data/202405/15T195735.970398/dca6bce9f88bc034a9883c0366144918f3757669e693fb259e62e99a8b29e592.png

DONE Learn to design a PCB

ruiqimao/keyboard-pcb-guide seems like a good place to start learning how to design this thing and use KiCad, but ultimately I decided to short-cut right to using ergogen once the controls in pbcnew were familiar enough; ergogen doesn't output a schematic (could one be generated, probably???) so I skipped the ruiqimao guide as soon as I felt comfortable with those UIs in kicad, knew how to add footprint libraries, etc.

so we'll need kicad installed, and I don't really need the 3D modeling capabilities, so I'll insert the minimal installation of it in to My NixOS configuration:

nix:tangle ~/nix/hm/kicad.nix
{ pkgs, ... }:

{
  home.packages = with pkgs; [ kicad-small ];
}

NEXT get kicad 3d models for the PCB , could be useful maybe for making a case....

INPROGRESS Use Ergogen to generate a PCB

Ergogen will place the footprints and generate the rat's nest by running the generate_board script =nix run .#generate_board=. The diode footprints provide both through-hole and SMD options on both sides of the board, which is nice, but it makes it a bit of a pain to route when the time comes, very fiddly work.

My Ergogen Atreoid YAML

Ergogen lets you define a columnar-ergonomic keyboard layout by defining stagger relative to each finger, and can output DXF files, STLs, KiCAD schematics/netlists... I only use it to generate what I call the "finger point cloud", basically the center-of-mass for each key which is then placed on to a 3d printed baseplate using a crappy python script I wrote.

here's a start from measuring my semi-curled finger lengths:

2025-07-07_22-08-30_4532fccba31fd7663a323e18aee406daf44e6b3ab2ee9008bceb0f7abda42793.png

yaml:tangle ergogen.yaml:collapse
units:
  # Parameters
  row_spacing: 1cy

  usb_cutout_x:  0
  usb_cutout_y: 72
  usb_cutout_r: 0

  # Constants
  choc_cap_x: 17.5
  choc_cap_y: 16.5

  choc_plate_thickness: 1.2
  mx_plate_thickness: 1.5

  apm_height: 33
  outline_slop: 2.5

  pcb_thickness: 1.6
  case_thickness: 1.5
  case_height: 10

The matrix is wired up thus: careful study of this with the docs alongside is probably good. First you set up the points, all the places you want to place a keyboard key, and how those keys should be routed in the rat's nest:

yaml:tangle ergogen.yaml:collapse
points:
  key: # each key across all zones will have these properties
    bind: 5
    width: choc_cap_x
    height: choc_cap_y
  zones:
    matrix:
      rows:
        bottom.row_net: R3
        home.row_net: R2
        top.row_net: R1
        num.row_net: R0
      columns:
        pinkycluster:
          key:
            stagger: 18
            column_net: C0
            mirror.column_net: C13
          rows:
            bottom.skip: true
            num.skip: true
            top.skip: false
        pinky:
          key:
            stagger: 9
            column_net: C1
            mirror.column_net: C12
          rows.bottom: $unset
        ring.key:
          stagger: -8
          column_net: C2
          mirror.column_net: C11
        middle.key:
          stagger: 8
          column_net: C3
          mirror.column_net: C10
        index.key:
          stagger: -14
          column_net: C4
          mirror.column_net: C9
        inner.key:
          bind: [44.5,10] # this is the "root" of the rest of the keys
          stagger: -4
          column_net: C5
          mirror.column_net: C8
        thumb:
          key:
            stagger: -6
            column_net: C6
            mirror.column_net: C7
          rows:
            bottom.key.bind: 10
            num: $unset
  rotate: -15
  mirror:
    ref: matrix_thumb_home
    distance: 50

After that you can define outlines and components for the hardware like switches, buttons, MCUs, etc below in Ergogen Hardware Details.

NEXT document this better

some day i'll document this better.

Hey Smell This, spend some time reverse engineering this config in the unofficial ergogen webui

NEXT how does this yaml thingy work

NEXT how i measured my fingers

NEXT philosophy of the layout and the layer system

NEXT document how i made the ratsnest

Ergogen Hardware Details

: add a bit about the footprint placement stuff and how it works in ergogen

These two footprints are the hotswap sockets for the keyswitches along with a set of thru-hole and surface mount diode locations

yaml:tangle ergogen.yaml:collapse
pcbs.monticello-drive.footprints.key:
    what: choc
    where: true
    params:
      from: "{{colrow}}"
      to: "{{column_net}}"
      hotswap: true
pcbs.monticello-drive.footprints.diodes:
    what: diode
    where: true
    adjust:
      rotate: 180
      shift: [0, -8]
    params:
      from: "{{colrow}}"
      to: "{{row_net}}"

There is a nice!nano v2 footprint module in my fork of ergogen, it's basically an arduino pro micro but uses the nRF GPIO names and has the three extra pins that we use to route the display i2c.

The pin->net definitions here are duplicated between the zmk and ergogen configuration, I guess, we'll have to de-duplicate this some day.

I tried also to position it between the point of the highest keys, the middle fingers' top row, but I still have to do some fiddly bullshit to line it all up. Maybe I can make this positioning nicer when I generate outlines.

yaml:tangle ergogen.yaml:collapse
pcbs.monticello-drive.footprints.nice_nano_v2:
    what: nice_nano_v2
    params:
      orientation: up
      pad_type: thru_hole

      P0_06: C0
      P0_08: C1
      P0_17: C2
      P0_20: C3
      P0_22: C4
      P0_24: C5
      P1_00: C6

      P0_10: C7
      P1_11: C8
      P1_13: C9
      P1_15: C10
      P0_02: C11
      P0_29: C12
      P0_31: C13

      P0_11: R0
      P1_04: R1
      P1_06: R2
      P0_09: R3

      P1_02: SDA
      P1_07: SCL
    adjust.rotate: -90
    adjust.shift: [0, -apm_height/2 + 15 + outline_slop]
    where.aggregate.parts:
    - ref: matrix_middle_num
    - ref: mirror_matrix_middle_num

: add an image showing the routes from MCU to columns and rows and display; maybe just write out a schematic some day...

The keyboard has a 2-prong JST or solder-point for hooking up a battery close enough to the mcu and OLED that you could stash it underneath or nearby, along with a small PCM12-compatible slider switch between the battery and the MCU's battery input.

yaml:tangle ergogen.yaml:collapse
pcbs.monticello-drive.footprints.battery_jst:
    what: jstph
    params:
      pos: BATTERY_P
      neg: GND
    where.aggregate.parts:
    - ref: matrix_thumb_home
    - ref: mirror_matrix_thumb_home
    adjust.shift: [0, 3U]

pcbs.monticello-drive.footprints.battery_sw:
    what: slider
    params:
        from: BATTERY_P
        to: BATIN
    adjust.shift: [1U, 17] # one mm lower than top of nice!nano
    where.aggregate.parts:
    - ref: matrix_middle_num
    - ref: mirror_matrix_middle_num

It has a 4 pin through-hole for an OLED display, you'll want to socket this thing, probably, though it may make your board thicker than you want:

yaml:tangle ergogen.yaml:collapse
pcbs.monticello-drive.footprints.oled_jumper:
    what: oled
    params:
      SDA: SDA
      SCL: SCL
    # adjust.rotate: -90
    adjust.shift: [-20, 0] # weird label is included in calc
    where.aggregate.parts:
    - ref: matrix_inner_num
    - ref: mirror_matrix_inner_num

It has a small momentary button routed to the bootloader reset functionality. This is only really necessary for the first time you flash it since the keymap has a bootloader combo on CTRL layer's top pinkycluster key, and you could also use a jumper to ground RST, but idk it's comforting in a way and doesn't cost much.

yaml:tangle ergogen.yaml:collapse
pcbs.monticello-drive.footprints.bootloader_rst:
    what: button
    params:
      from: GND
      to: RST
      side: F
    adjust.shift: [1U, 10]
    where.aggregate.parts:
    - ref: matrix_middle_num
    - ref: mirror_matrix_middle_num

Board Outline

holy magic numbers, batman! this was iterated on in the Ergogen web ui, you'll end up changing it if you change any of the point settings.

  • There are the main outlines (from AAA to BBB) where we make a sort of rounded bun by overlapping two circles then cut the main body with a third circle.

  • (BBB to CCC) This is filleted to have rounded corners, and a cutout is added for the USB-C connector of a flush-fit MCU

  • (CCC to DDD) demarcate the footprints of the choc keyswitches

  • (DDD to EEE) provides an outline that is used in the top case to add an extrusion to make it easy to hit the board reset button.

  • (EEE to FFF) define case cutouts for the hardware on the board

  • the insets (FFF to GGG) are used to create the bottom case that "wraps" around the bottom of the board.

  • (GGG to end) define the top case as a bunch of those hardware outline definitions and a "key exclusion area" extended from the points of the keyboard.

yaml:tangle ergogen.yaml:collapse
pcbs.monticello-drive.outlines.main:
  outline: combined

outlines:
  # ===================AAA===========================
  _bottom_arch_circle:
    - what: circle
      radius: 490
      where:
        aggregate.parts:
        - ref: matrix_middle_bottom
        - ref: mirror_matrix_middle_bottom
        shift: [0, -415]
  _top_arch_circle:
    - what: circle
      radius: 197
      where:
        aggregate.parts:
        - ref: matrix_middle_bottom
        - ref: mirror_matrix_middle_bottom
        shift: [0, 140]
  _main_body_circle:
    - what: circle
      radius: 147
      where:
        aggregate.parts:
        - ref: matrix_middle_bottom
        - ref: mirror_matrix_middle_bottom
        shift: [0, 0]
  _main: [
      _top_arch_circle,
      ~_bottom_arch_circle,
      ~_main_body_circle
  ]
  # ================== BBB ====================
  _fillet:
    - what: outline
      name: _main
      fillet: 6
  _usb_c_cutout:
    - what: rectangle
      size: [9.28, 6.67]
      where: &usbanchor
        aggregate.parts:
        - ref: matrix_middle_bottom
        - ref: mirror_matrix_middle_bottom
        shift: [ usb_cutout_x, usb_cutout_y ]
        rotate: usb_cutout_r
  combined: [
      _fillet,
      -_usb_c_cutout
  ]
  # ================== CCC ====================
  _switch_cutouts:
    - what: rectangle
      where: true
      asym: source
      size: 14 # Plate cutouts are 14mm * 14mm for both MX and Choc
      bound: false
  switch_plate:
    [ combined, -_switch_cutouts]
  # ================== DDD ====================
  _power_reset_button:
    - what: circle
      radius: 1.5
      adjust.shift: [1U, 10]
      where.aggregate.parts:
      - ref: matrix_middle_num
      - ref: mirror_matrix_middle_num
  # ================== EEE ====================
  _usb_c_cutout_case:
    - what: outline
      name: _usb_c_cutout
      expand: 2
  _oled_cutout:
    - adjust.shift: [0, -7]
      where.aggregate.parts:
      - ref: matrix_inner_num
      - ref: mirror_matrix_inner_num
      size: [ 40, 14 ]
      what: rectangle
  _promicro:
  - adjust.rotate: -90
    adjust.shift: [0, -apm_height/2 + 15 + outline_slop]
    where.aggregate.parts:
    - ref: matrix_middle_num
    - ref: mirror_matrix_middle_num
    what: rectangle
    size: [40,19]
  _power_switch:
    - what: rectangle
      size: [8, 6]
      adjust.shift: [1U, 17] # one mm lower than top of nice!nano
      where.aggregate.parts:
      - ref: matrix_middle_num
      - ref: mirror_matrix_middle_num
  _power_switch_outer_case:
    - what: outline
      name: _power_switch
      expand: 2

  _combined_case_expand:
    - what: outline
      name: _fillet
      expand: 2
  expanded_case:
    [ _combined_case_expand]
  # ================== FFF ====================
  inset:
    - what: outline
      name: _fillet
      expand: -2
  inset_bottom:
    - what: outline
      name: _fillet
      expand: -4
  # ================== GGG ====================
  _key_exclusion:
    - what: rectangle
      where: true
      asym: source
      size: 19
      bound: false

  case_top:
    [ combined, -_key_exclusion, -_oled_cutout, -_promicro, -_power_switch, -_power_reset_button]

Many of those outlines are used by these case definitions (and of course the PCB below) but this case is basically designed to wrap around the sides of the board, and then there is an in-set cover on the top, all of this is held together with mounting putty until i decide to do something better, perhaps glued-in magnets or something. The problem is that the case footprints don't fit on my home printer so it has to be done in multiple parts and glued together.

yaml:tangle ergogen.yaml:collapse
cases:
  bottom_outer_case:
    - name: expanded_case
      extrude: case_height + 2 # dark laughter magic number
    - name: combined
      extrude: case_height - case_thickness
      shift: [0, 0, case_thickness]
      operation: subtract
    - name: _usb_c_cutout_case
      extrude: case_height
      shift: [0,2,case_thickness]
      operation: subtract
    - name: _power_switch_outer_case
      extrude: case_height
      shift: [0,2,pcb_thickness+case_thickness]
      operation: subtract
    - name: inset_bottom
      extrude: case_height - pcb_thickness - case_thickness
      operation: subtract
  top_inner_case:
    - name: case_top
      extrude: case_height - pcb_thickness
    - name: inset
      extrude: case_height - case_thickness - pcb_thickness
      operation: subtract
    - name: _power_reset_button
      extrude: case_height - 1.4 - 0.45 - case_thickness
      shift: [0,0, 1.4 + 0.25 ]

Sorry about the magic numbers...

BoM for Revision 1.1

These are one-time purchases that you can re-use between board revisions if you're careful, but maybe also keep a couple around in case you get a wild hair up your ass to make a new revision of the board:

1 25.00 BoardSource Nice!Nano v2 Nice!Nano v2 (gl finding these in stock)
1 14.00 BoardSource SuperMini NRF52840 n!n2 alternative, maybe
1 55 Boardsource Choc keycaps set
5 6 Boardsource Choc1 compatible 10x i like the browns
1 6 BoardSource 128x32 OLED
1 5.50 BoardSource 110mAh battery or bring your own like i did

I tried that SuperMini as an nice!nano v2 alternative for rev 1.1 and it's basically great, cheaper, and more available, except I think the 3 extra "inner" pins are in a different location, and the behavior of the bright blue LEDs on the board is much more annoying.

Soldering a board revision will consume these, consider buying a couple extra of each to build up a supply of spare parts even though some things ship 2x, pcbway MoQ is 5x. You'll never regret owning 72 extra through hole diodes!

Q C ID Desc
2 8.40 BoardSource Mill-Max Socket Set pin & socket set for APM/etc doesn't break out SDA/SCL
1 1.00 BoardSource PTS526 2x 5.2mm reset momentary buttons
5 1.50 BoardSource CPG135001S30 Choc hot swap sockets 10 pack
5 0.50 BoardSource TH diodes 10x pack Diodes; SMD or through-hole both work, get em wherever
1 2 Boardsource SMD slide switch

you might want to build this with SMD diodes, it's easier to do it with thru-holes but you could make a Monticello Drive that doesn't have any of the diode thru-hole or back SMD pads and slap a big silkscreen on it to end up with a pretty slick looking little device. for now, especially while i'm prototyping, i'm sticking to thru-hole diodes.

INPROGRESS Building the Monticello Drive

Having iterated on a base ergogen.yaml matrix to something you enjoy typing on using Revision Zero to prototype the keyboard, you now want a portable reliable keyboard.

You've added the hardware components to the board and generated the outlines following this document. Now it's time to open up kicad and route a PCB and have 5 PCBs manufactured of a prototype. Make sure when you order on PCBway you put those files in to version control and add a git tag or something. You'll have to just experiment with the routing and get good at it, it takes a while to get the hang of it and figure out how to design the board well. The repo has mine, you'll like to use it as inspiration in the [rev1.1] tag.

I'll have to do a better job explaining this some day!

With a keyboard built, you can flash the Rev1 Firmware to it.

NEXT upstream or literize the customizations and footprints i added to ergogen clone

there is a nice!nano compatible module and a nix flake in there at least. when prototyping, the nice_nano_v2 module needed to be replaced with a promicro.

DONE Revision One.1

need to think about "softer" revisions, and tweaks i should make to the layout itself, not just the board process and feature set! write a report of 1.0

switch back to through-hole mcu connection, gonna use those 8.5mm headers until i'm confident in a build all the other rev1_1 tagged tasks below of course

DONE flip the OLED connector around

woops

DONE add a slider switch for the power

DONE figure out why the battery wireup doesn't work

It works when I use the small BATIN leads that I can't attach to headers, but not the header-able RAW pin... annoying.

i think this might have just been my flush-solder job, possibly

DONE switch footprint to choc hotswap sockets

NEXT fit the trackpoint module in to all of this.

i could make those cuts on the rev2 board so that there is a plane that the module plate could be soldered to

or it could be a part of the 3d printed model case so that it could be raised up to a more comfortable height without making internal cuts

i was thinking about it and

the board is two parts

  • one is the little sensor itself with four larger pins with a flush connector

  • one is the control board that spits out ps/2 and a bunch of other shit. we only need like one pin and the reset/init circuit, and like vcc/gnd, i think. i have this written down somewhere or can find it again in that cursed discord.

the sensor board needs to be mounted to the case or the pcb; if it's pcb mounted it ends up being screwed until it's flush tight in to some thru-holes w/ requisite tolerance for the screws, then those are filed off

the connection between the sensor and the processor is a bit complicated because these connectors are all under-side mounted w/o edge leads or through-holes;

i could leave it in its default package and carefully measure out the location to put down a zif connector or solder mess for the four pins on this zif that i need to route to somewhere on the mcu. i hope i only need one data pin for the ps/2 data, cause that's all I have!

DONE add outlines to the YAML file

DONE figure out why the silk screens for the nice nano etc weren't on the final boards, add them back, silkscreen something cool on here

WAITING get ergogen to generate schematics.

WAITING convert to choc v2

maybe idk; sockets while we're at it

WAITING kind of enamored by these PG1316 key switches I saw on reddit, something to consider in future sub-revisions...

waiting on a group buy or something, it's kind of the only reason I check reddit any more lol.

WAITING produce AxiDraw work from the SVG output of the PCB and schematic

Rev1 Firmware

The shield module is in ./zmk-config and should be detangled below. It's basically the same as the rev0's, but the matrix is laid out a bit differently compared to how i set it up in rev0, basically just in the pin layout for the columns are mirrored or flipped in some places and also scl/sda for the oleds are flipped. like i said, basically the same.

The metadata file for the keyboard shield makes sure that zmk knows that it needs to build the keyboard.

yaml:tangle zmk-config/boards/shields/monticello_drive_rev1/monticello_drive_rev1.zmk.yml:mkdirp yes
file_format: "1"
id: monticello_drive_rev1
name: r_1.0
type: shield
url: https://arcology.garden/my/atreoid
requires: [pro_mciro]
exposes:
  - i2c_oled
features:
  - keys
  - display

some magic bullshit lol, configuring the OLED and basic stuff no one should need to touch:

conf:tangle zmk-config/boards/shields/monticello_drive_rev1/Kconfig.shield
config SHIELD_MONTICELLO_DRIVE_REV1
  def_bool $(shields_list_contains,monticello_drive_rev1)
conf:tangle zmk-config/boards/shields/monticello_drive_rev1/Kconfig.defconfig
if SHIELD_MONTICELLO_DRIVE_REV1

config ZMK_KEYBOARD_NAME
    default "Monty Drive r1.1"

if ZMK_DISPLAY

config ZMK_DISPLAY
    select LV_USE_CONT
    select LV_FONT_MONTSERRAT_26
    select LV_FONT_MONTSERRAT_16
    select LV_USE_LABEL
    select LV_USE_IMG

config I2C
    default y

config SSD1306
    default y

config SSD1306_REVERSE_MODE
    bool
    default n

endif # ZMK_DISPLAY

if LVGL

config LV_Z_VDB_SIZE
    default 64

config LV_DPI_DEF
    int
    default 148

config LV_Z_BITS_PER_PIXEL
    default 1

choice LV_COLOR_DEPTH
    default LV_COLOR_DEPTH_1
endchoice

endif # LVGL

endif

The keyboard is configured in a fairly straightforward, if baroque manner using something like the Linux kernel build process. these devicetree files we're working with are a format that is easy to use to merge multiple trees of configuration, to reach in to the device tree to re-configure things, etc and then generate code based on that tree... i think? it's kind of like how NixOS constructs its config attrset merging a bunch of files together to construct an entire configuration and then build a derivation recursively out to each dependency in the attrset until a root file system or activation profile is provided.

Usually you just need to configure the pin mapping in the matrix you've wired up. I also define a transformation matrix so that the left hand and right hand are defined on their own rows for legibility.

This makes the error messages coming out of the gen_defines.py script that zmk uses to generate C files from these device trees much worse than they are otherwise, but it allows for much more legible documents and keymap configuration, and the errors you get if you make a syntax error in the keymap file are already a pain to parse as it is!

dts:tangle zmk-config/boards/shields/monticello_drive_rev1/monticello_drive_rev1.overlay
#include <dt-bindings/zmk/matrix_transform.h>

/ {
    chosen {
        zephyr,display = &oled;
        zmk,kscan = &kscan0;
        zmk,matrix-transform = &atreoid_scan;
        zmk,battery = &vbatt;
    };

    kscan0: kscan_0 {
        compatible = "zmk,kscan-gpio-matrix";
        diode-direction = "col2row";
        wakeup-source;

        col-gpios
        = <&pro_micro  1 GPIO_ACTIVE_HIGH>
        , <&pro_micro  0 GPIO_ACTIVE_HIGH>
        , <&pro_micro  2 GPIO_ACTIVE_HIGH>
        , <&pro_micro  3 GPIO_ACTIVE_HIGH>
        , <&pro_micro  4 GPIO_ACTIVE_HIGH>
        , <&pro_micro  5 GPIO_ACTIVE_HIGH>
        , <&pro_micro  6 GPIO_ACTIVE_HIGH>

        , <&pro_micro 16 GPIO_ACTIVE_HIGH>
        , <&pro_micro 14 GPIO_ACTIVE_HIGH>
        , <&pro_micro 15 GPIO_ACTIVE_HIGH>
        , <&pro_micro 18 GPIO_ACTIVE_HIGH>
        , <&pro_micro 19 GPIO_ACTIVE_HIGH>
        , <&pro_micro 20 GPIO_ACTIVE_HIGH>
        , <&pro_micro 21 GPIO_ACTIVE_HIGH>
        ;

        row-gpios
        = <&pro_micro  7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
        , <&pro_micro  8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
        , <&pro_micro  9 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
        , <&pro_micro 10 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
        ;
    };

    atreoid_scan: matrix_transform {
        compatible = "zmk,matrix-transform";
        rows = <4>;
        columns = <14>;
        map = <
        // left hand
        RC(1,0)		RC(0,1)		RC(0,2)		RC(0,3)		RC(0,4)		RC(0,5)
        RC(2,1)   	RC(1,1)		RC(1,2)		RC(1,3)		RC(1,4)		RC(1,5)		RC(1,6)
        		 		RC(2,1)		RC(2,2)		RC(2,3)		RC(2,4)		RC(2,5)		RC(2,6)
        							RC(3,2)		RC(3,3)		RC(3,4)		RC(3,5)		RC(3,6)

        // right hand
                    RC(0,8)     RC(0,9)     RC(0,10)    RC(0,11)    RC(0,12)    RC(1,13)
        RC(1,7)     RC(1,8)     RC(1,9)     RC(1,10)    RC(1,11)    RC(1,12)    RC(2,13)
        RC(2,7)     RC(2,8)     RC(2,9)     RC(2,10)    RC(2,11)    RC(2,12)
        RC(3,7)     RC(3,8)     RC(3,9)     RC(3,10)    RC(3,11)
        >;
    };

    vbatt: vbatt {
        compatible = "zmk,battery-nrf-vddh";
    };
};

The shield definition also includes some magic to configure the OLED display with the i2c running on non-standard pins; I use two of three in the middle of the PCB rather than the standard arduino i2c pins, those are in the matrix array.

dts:tangle zmk-config/boards/shields/monticello_drive_rev1/monticello_drive_rev1.overlay
&pro_micro_i2c {
    status = "okay";

    oled: ssd1306@3c {
        compatible = "solomon,ssd1306fb";
        reg = <0x3c>;
        width = <128>;
        height = <32>;
        segment-offset = <0>;
        page-offset = <0>;
        display-offset = <0>;
        multiplex-ratio = <31>;
        segment-remap;
        com-invdir;
        com-sequential;
        inversion-on;
        prechargep = <0x22>;
    };
};

// remember that when I build a PCB version of this that i had to swap SDA and SCL on the board!
&pinctrl {
    i2c0_default: i2c0_default {
        group1 {
            psels =
            <NRF_PSEL(TWIM_SDA, 1, 2)>,
            <NRF_PSEL(TWIM_SCL, 1, 7)>;
        };
    };

    i2c0_sleep: i2c0_sleep {
        group1 {
            psels =
            <NRF_PSEL(TWIM_SDA, 1, 2)>,
            <NRF_PSEL(TWIM_SCL, 1, 7)>;
            low-power-enable;
        };
    };
};

NEXT 3D printed board case

ergogen can output simple cases based on the DXF outlines; the ergogen.yaml has some outlines and cases. generating the STLs for print is kind of a pain, i owe it to myself and anyone else to document the process i went through to generate the cases i defined in the Outline documentation above.

  • ergogen will generate jscad files

  • run an old fork of openjscad to generate the STL (this should fold in to the ergogen cli)

  • figure out how to fit it in to your printer

  • using openscad to modify it with BOSL2 jointers etc may require you to go in to tinkercad or something to make the STL valid enough for openscad/CGAL to work with. this is a pain and probably could be alleviated by more careful case design to generate valid solids, or the jscad driver could be improved. idk. tinkercad is free but requires an account and it doesn't export the model with its prior origin.