Compare commits

...

3 Commits

24 changed files with 646 additions and 401 deletions

2
.gitignore vendored
View File

@ -24,7 +24,7 @@ dist
/ergogen/output /ergogen/output
# Kicad temporary files # Kicad temporary files
/kicad/*-backups/ /kicad/*/*-backups/
*.bak *.bak
*.kicad_pcb-bak *.kicad_pcb-bak
*.kicad_sch-bak *.kicad_sch-bak

View File

@ -5,10 +5,19 @@ Custom ergonomic mechanical keyboard with low-profile (Choc) switches.
## Versions ## Versions
- **eepyBoard v1.0: Hardware revision 1** - **eepyBoard v1.0: Hardware revision 1**
- Mostly ortholinear layout with 13 columns - Mostly ortholinear layout with 13 columns (73 keys + rotary encoder)
- Bottom row: two regular keys + four thumb keys (per side, symmetrical) - Bottom row: two regular keys + four thumb keys (per side, symmetrical)
- Partial F-key row (Esc, F1-F8) and rotary encoder - Partial F-key row (Esc, F1-F8) and rotary encoder
- RP2040 controller board in upper right corner - RP2040 controller board in upper right corner
- **eepyBoard v1.1: Hardware revision 2**
- Column staggered split layout (single board) with a total of 15 columns and 6 rows (88 keys + 2 rotary encoders)
- Each side has 7 columns by 5 rows (not counting function keys and arrow keys)
- Bottom row: three regular keys + four thumb keys on each side, and an additional centered thumb key
- Full F-key row (Esc, F1-F12)
- 4 arrow keys in the center
- 2 pressable rotary encoders (one in the center of the keyboard, one in the upper right)
- Compact RP2040 "Pico Mini" controller board in the center
## Repository structure ## Repository structure
@ -36,31 +45,35 @@ Copy the file to `kicad/eepyboard.kicad_pcb`.
Open the file in KiCad (create a project if non exists yet). Finalize the PCB in KiCad. Open the file in KiCad (create a project if non exists yet). Finalize the PCB in KiCad.
1. Run the Design Rules Checker. Check the errors. Most of them can be ignored/excluded. 1. Round off the bottom corners of the board outline:
- Remove the bottom center line and the two small fillet arcs next to it.
- Select the two remaining bottom lines. Right click them and use "Shape Modification -> Extend Lines to Meet".
- Right click the two angled lines again. Use "Shape Modification -> Fillet Lines..." with a 200mm radius.
2. Run the Design Rules Checker. Check the errors. Most of them can be ignored/excluded.
- All "Footprint not found in libraries" can be ignored completely. This is due to how Ergogen generates the PCB. - All "Footprint not found in libraries" can be ignored completely. This is due to how Ergogen generates the PCB.
2. Add VCC and GND planes. 3. Add VCC and GND planes.
- Menu: `File -> Board Setup` - Menu: `File -> Board Setup`
- On "Physical Stackup", change the copper layer number to 4. - On "Physical Stackup", change the copper layer number to 4.
- On "Board Editor Layers", change the type of `In1.Cu` and `In2.Cu` to "power plane". - On "Board Editor Layers", change the type of `In1.Cu` and `In2.Cu` to "power plane".
- NOTE: `In1.Cu` will be the VCC plane, `In2.Cu` will be a GND plane. - NOTE: `In1.Cu` will be the VCC plane, `In2.Cu` will be a GND plane.
3. Add filled zones to the VCC and GND planes. 4. Add filled zones to the VCC and GND planes.
- Select the `In1.Cu` layer. - Select the `In1.Cu` layer.
- Use the "Add a filled zone" tool and draw a rectangle that contains the entire board. Assign the zone to VCC. - Use the "Add a filled zone" tool and draw a rectangle that contains the entire board. Assign the zone to VCC.
- Repeat the same process for the `In2.Cu` layer and assign the zone to GND. - Repeat the same process for the `In2.Cu` layer and assign the zone to GND.
- The zones don't need to be filled just yet. - The zones don't need to be filled just yet.
4. Add another filled zone on the `B.Cu` layer and assign it to GND. 5. Add another filled zone on the `B.Cu` layer and assign it to GND.
5. Route all signal traces (no VCC or GND yet). Recommended order: 6. Route all signal traces (no VCC or GND yet). Recommended order:
- Matrix rows (on `B.Cu`) - Matrix rows (on `B.Cu`)
- Matrix columns (with vias on `F.Cu`) - Matrix columns (with vias on `F.Cu`)
- NeoPixel data pins - NeoPixel data pins
- Connect everything to the MCU. - Connect everything to the MCU.
6. Route VCC traces. 7. Route VCC traces.
- Connect the VCC traces between the NeoPixel chips and the capacitors with a 0.750 mm track. - Connect the VCC traces between the NeoPixel chips and the capacitors with a 0.750 mm track.
- Place free-standing vias (Ctrl+Shift+V) in the middle of the just created VCC traces. - Place free-standing vias (Ctrl+Shift+V) in the middle of the just created VCC traces.
7. Route GND traces. 8. Route GND traces.
- Draw short GND traces with a 0.750 mm track and a via at the end next to the GND pads of the NeoPixel chips. - Draw short GND traces with a 0.750 mm track and a via at the end next to the GND pads of the NeoPixel chips.
8. Fill all zones by pressing B. Make sure that all nets are routed. 9. Fill all zones by pressing B. Make sure that all nets are routed.
9. Run the Design Rules Checker and make sure there are no (relevant) violations. 10. Run the Design Rules Checker and make sure there are no (relevant) violations.
#### Export Gerber files #### Export Gerber files

View File

@ -8,7 +8,7 @@ meta:
# Required version of Ergogen # Required version of Ergogen
engine: "4.1.0" engine: "4.1.0"
# Version of the board # Version of the board
version: "1.0" version: "1.1"
author: binaryDiv author: binaryDiv
# Define units and default values used throughout this file # Define units and default values used throughout this file
@ -29,268 +29,409 @@ units:
$default_spread: cx $default_spread: cx
$default_padding: cy $default_padding: cy
# Vertical offset between function keys and primary keys # Vertical offset between function key row and main zone
function_zone_offset: 3 function_offset: 3
# Define the points (i.e. keys) # Horizontal offset/gap between function key groups (Esc, F1-F4, F5-F8, F9-F12)
function_gap: 6
# Distance between left and right half of the main zone
split_gap_width: 2cx + 3
# Radius of rotary encoder knob (just for visualization)
rotary_encoder_radius: 10
# Define the points (primarily the keys, but also some auxiliary points for
# placement of other components, e.g. rotary encoders and the controller board)
points: points:
key: key:
# Tag all points as "is_key" unless overwritten # Tag all points as "is_key" unless overwritten
tags: [ is_key ] tags: [ is_key ]
# Use 5V as VCC for all RGB LEDs, except for the first one in the chain
led_vcc_net: VCC
zones: zones:
# Primary keyboard zone # Main keyboard zone
primary: main:
# Set anchor to fix placement on KiCad sheet # Set anchor to fix placement on KiCad sheet
anchor: anchor:
shift: [ 100, -150 ] shift: [ 75, -175 ]
# Key rows (from bottom to top) # Key rows (from bottom to top)
rows: rows:
# Modifier row (Ctrl, ..., but excluding the thumb keys) # Modifier row (Ctrl etc., but excluding the thumb keys)
mods: mods:
row_net: GP17 row_net: ROW1
led_previous_key: "{{zone.name}}_{{neighbor_col_left}}_{{row}}" led_next_key: "{{zone.name}}_{{neighbor_col_left}}_{{row}}"
tags: [ is_key, flip_led ]
# Bottom letter row (Shift, ZXCV...) # Bottom letter row
bottom: bottom:
row_net: GP16 row_net: ROW2
led_previous_key: "{{zone.name}}_{{neighbor_col_right}}_{{row}}" led_next_key: "{{zone.name}}_{{neighbor_col_right}}_{{row}}"
tags: [ is_key, flip_led ]
# Middle/home letter row (Caps Lock, ASDFG...) # Home letter row
home: home:
row_net: GP15 row_net: ROW3
led_previous_key: "{{zone.name}}_{{neighbor_col_left}}_{{row}}" led_next_key: "{{zone.name}}_{{neighbor_col_left}}_{{row}}"
tags: [ is_key, flip_led ]
# Top letter row (Tab, QWERT...) # Top letter row
top: top:
row_net: GP14 row_net: ROW4
led_previous_key: "{{zone.name}}_{{neighbor_col_right}}_{{row}}" led_next_key: "{{zone.name}}_{{neighbor_col_right}}_{{row}}"
# Number row (`, 12345...)
numbers:
row_net: GP13
led_previous_key: "{{zone.name}}_{{neighbor_col_left}}_{{row}}"
tags: [ is_key, flip_led ] tags: [ is_key, flip_led ]
# Number row
numbers:
row_net: ROW5
led_next_key: "{{zone.name}}_{{neighbor_col_left}}_{{row}}"
# Key columns (from left to right) # Key columns (from left to right)
columns: columns:
# Left-most column (`, Tab, Caps Lock, Shift, Ctrl) # Left-most column (extra keys)
left:
key.column_net: COL1
key.neighbor_col_right: zero
rows.numbers.led_next_key: main_left_top
rows.home.led_next_key: main_left_bottom
# Use placeholder to mark end of the RGB LED chain (unused net name)
rows.mods.led_next_key: END_OF_CHAIN
# Modifier column (`, Tab, Shift, etc.)
zero: zero:
key.column_net: GP8 key.stagger: 1/8cy
key.column_net: COL2
key.neighbor_col_left: left
key.neighbor_col_right: one key.neighbor_col_right: one
rows.numbers.led_previous_key: function_esc_default
rows.home.led_previous_key: primary_zero_top
rows.mods.led_previous_key: primary_zero_bottom
# First letter column (1, Q) # First letter column (1, Q)
one: one:
key.column_net: GP7 key.stagger: 1/8cy
key.column_net: COL3
key.neighbor_col_left: zero key.neighbor_col_left: zero
key.neighbor_col_right: two key.neighbor_col_right: two
# Second letter column (2, W) # Second letter column (2, W)
two: two:
key.column_net: GP6 key.stagger: 1/8cy
key.column_net: COL4
key.neighbor_col_left: one key.neighbor_col_left: one
key.neighbor_col_right: three key.neighbor_col_right: three
rows.mods.skip: true rows.mods.skip: true
# Third letter column (3, E) # Third letter column (3, E)
three: three:
key.column_net: GP5 key.column_net: COL5
key.neighbor_col_left: two key.neighbor_col_left: two
key.neighbor_col_right: four key.neighbor_col_right: four
rows.mods.skip: true rows.mods.skip: true
# Fourth letter column (4, R) # Fourth letter column (4, R)
four: four:
key.column_net: GP4 key.stagger: -(1/4cy)
key.column_net: COL6
key.neighbor_col_left: three key.neighbor_col_left: three
key.neighbor_col_right: five key.neighbor_col_right: five
rows.mods.skip: true rows.mods.skip: true
# Fifth letter column (5, T) # Fifth letter column (5, T)
five: five:
key.column_net: GP3 key.stagger: -(1/4cy)
key.column_net: COL7
key.neighbor_col_left: four key.neighbor_col_left: four
key.neighbor_col_right: six key.neighbor_col_right: six
rows.top.led_next_key: arrow_keys_up
rows.bottom.led_next_key: arrow_keys_down
rows.mods.skip: true rows.mods.skip: true
# (Gap with arrow keys in the middle: Matrix column 8)
# Sixth letter column (6, Y) # Sixth letter column (6, Y)
six: six:
key.column_net: GP2 key.spread: cx + split_gap_width
key.column_net: COL9
key.neighbor_col_left: five key.neighbor_col_left: five
key.neighbor_col_right: seven key.neighbor_col_right: seven
rows.home.led_next_key: arrow_keys_right
rows.mods.skip: true rows.mods.skip: true
# Seventh letter column (7, U) # Seventh letter column (7, U)
seven: seven:
key.column_net: GP1 key.stagger: 1/4cy
key.column_net: COL10
key.neighbor_col_left: six key.neighbor_col_left: six
key.neighbor_col_right: eight key.neighbor_col_right: eight
rows.mods.skip: true rows.mods.skip: true
# Eighth letter column (8, I) # Eighth letter column (8, I)
eight: eight:
key.column_net: GP0 key.stagger: 1/4cy
key.column_net: COL11
key.neighbor_col_left: seven key.neighbor_col_left: seven
key.neighbor_col_right: nine key.neighbor_col_right: nine
rows.mods.skip: true rows.mods.skip: true
# Nineth letter column (9, O) # Nineth letter column (9, O)
nine: nine:
key.column_net: GP25 key.column_net: COL12
key.neighbor_col_left: eight key.neighbor_col_left: eight
key.neighbor_col_right: ten key.neighbor_col_right: ten
rows.mods.skip: true rows.mods.skip: true
# Tenth letter column (0, P) # Tenth letter column (0, P)
ten: ten:
key.column_net: GP24 key.stagger: -(1/8cy)
key.column_net: COL13
key.neighbor_col_left: nine key.neighbor_col_left: nine
key.neighbor_col_right: eleven key.neighbor_col_right: eleven
rows.mods.skip: true rows.mods.led_next_key: mirror_thumb_one
# Eleventh letter column (-, [) # Eleventh letter column (-, [)
eleven: eleven:
key.column_net: GP23 key.stagger: -(1/8cy)
key.column_net: COL14
key.neighbor_col_left: ten key.neighbor_col_left: ten
key.neighbor_col_right: twelve key.neighbor_col_right: twelve
rows.mods.led_previous_key: thumb_mirror_one_default
# Twelfth letter column (=, ]) # Twelfth letter column (=, ])
twelve: twelve:
key.column_net: GP22 key.stagger: -(1/8cy)
key.column_net: COL15
key.neighbor_col_left: eleven key.neighbor_col_left: eleven
rows.top.led_previous_key: primary_twelve_numbers rows.top.led_next_key: main_twelve_home
rows.bottom.led_previous_key: primary_twelve_home rows.bottom.led_next_key: main_twelve_mods
# Thumb fan # Thumb fan
thumb: thumb:
# Position thumb keys based on the (non-existant) third key in the modifier row # Position thumb keys based on the third key in the modifier row
anchor: anchor:
ref: primary_two_mods ref: main_one_mods
shift: [ 2, 0 ] shift: [ cx + 1, 0 ]
# Rotate the thumb keys around the bottom-left corner of the key # Zone-wide key settings
key: key:
row_net: GP17 row_net: ROW1
tags: [ is_key, flip_led ] # Rotate the thumb keys around the bottom-left corner of the key
origin: [ -0.5cx, -0.5cy ] origin: [ -0.5cx, -0.5cy ]
splay: -4 splay: -6
# Define thumb keys # Define thumb keys
columns: columns:
one: one:
key.column_net: GP6 key.column_net: COL4
key.mirror.column_net: GP24 key.mirror.column_net: COL12
key.led_previous_key: primary_one_mods key.led_next_key: main_one_mods
key.mirror.led_previous_key: thumb_mirror_two_default key.mirror.led_next_key: mirror_thumb_two
two: two:
key.column_net: GP5 key.column_net: COL5
key.mirror.column_net: GP25 key.mirror.column_net: COL11
key.led_previous_key: thumb_one_default key.led_next_key: thumb_one
key.mirror.led_previous_key: thumb_mirror_three_default key.mirror.led_next_key: mirror_thumb_three
three: three:
key.column_net: GP4 key.column_net: COL6
key.mirror.column_net: GP0 key.mirror.column_net: COL10
key.led_previous_key: thumb_two_default key.led_next_key: thumb_two
key.mirror.led_previous_key: thumb_mirror_four_default key.mirror.led_next_key: mirror_thumb_four
four: four:
key.column_net: GP3 key.column_net: COL7
key.mirror.column_net: GP1 key.mirror.column_net: COL9
key.led_previous_key: thumb_three_default key.led_next_key: thumb_three
key.mirror.led_previous_key: thumb_four_default key.mirror.led_next_key: thumb_center
# Mirror the thumb fan # Mirror the thumb fan
mirror: mirror:
ref: primary_five_numbers ref: main_five_bottom
shift: [ 0.5cx, 0.5cx ] shift: [ 0.5cx, 0.5cy ]
distance: 1cx distance: split_gap_width
# Function key row above the primary zone (Esc + 8 more keys) # Single key centered between thumb keys
# (Note that the keys are only named "f[1-8]" for convenience and are not a full set of F1-F12 keys. They can be thumb_center:
# used as F-keys when needed, but will more likely be used for macros and other special functions.)
function:
# Position function key row above primary zone with 3mm of vertical offset
anchor: anchor:
ref: primary_zero_numbers # Center between the two inner thumb keys
shift: [ 0, cy + function_zone_offset ] aggregate.parts:
- thumb_four
- mirror_thumb_four
shift: [ 0, -0.25cy ]
# Key settings (instead of rows/columns)
key: key:
row_net: GP12 width: 1.5kcx
row_net: ROW1
column_net: COL8
led_next_key: thumb_four
tags: [ is_key, 1_5u ]
# Arrow keys in the middle of the keyboard
# In the matrix, these keys act as 4 keys in a single column (column 8, row 2 to 5)
arrow_keys:
anchor:
# Center between the V and B keys, then shift down half a key
aggregate.parts:
- main_five_bottom
- main_six_bottom
shift: [ 0, -cy/2 ]
# Assign all keys to the same column
key:
column_net: COL8
rows:
# Bottom row: Only the down key
bottom:
row_net: ROW2
tags: [ is_key, flip_led ]
# Center row: Left and right keys (shifted to the left to center them)
center:
adjust.shift: [ -cx/2, 0 ]
# Use ROW3 for the left key and ROW5 for the right key (overridden in columns)
row_net: ROW3
# Top row: Only the up key
top:
row_net: ROW4
tags: [ is_key, flip_led ]
columns:
# Left column: Up, left, down keys
left:
rows.top:
name: arrow_keys_up
led_next_key: main_six_top
rows.center:
name: arrow_keys_left
led_next_key: main_five_home
rows.bottom:
name: arrow_keys_down
led_next_key: main_six_bottom
# Right column: Only right key (assigned to matrix row 5)
right:
rows.top.skip: true
rows.center:
name: arrow_keys_right
row_net: ROW5
led_next_key: arrow_keys_left
rows.bottom.skip: true
# Function key row above the main zone (Esc, F1-F12)
function:
# Position function key row above main zone with 3mm of vertical offset
anchor:
# To center the F keys on the top of the keyboard:
# - Get center point between keys 3 and 8 (they have the highest staggering)
# - Shift upwards by one key + an offset to have a small gap
# - Shift to the left by 7 keys (Esc, F1-F6) + the gaps between the F key groups.
aggregate.parts:
- main_three_numbers
- main_eight_numbers
shift: [ -6.5cx - 2 * function_gap, cy + function_offset ]
# Single row zone: Assign row net
key:
row_net: ROW6
tags: [ is_key, flip_led ]
# Define function keys # Define function keys
columns: columns:
# Escape key # Escape key
esc: esc:
key.column_net: GP8 key.shift: [ 0, -cy/8 ]
key.led_previous_key: function_f1_default key.column_net: COL1
key.led_next_key: function_f1
# Esc is the first key in the RGB LED chain.
# The first LED needs to run on a lower voltage (see explanation below).
key.led_vcc_net: VCC_first_led
# Block of 4 keys with 6mm offset # Block of 4 keys with gap
f1: f1:
key.spread: cx + 6 key.spread: cx + function_gap
key.column_net: GP7 key.column_net: COL2
key.led_previous_key: function_f2_default key.led_next_key: function_f2
f2: f2:
key.column_net: GP6 key.column_net: COL3
key.led_previous_key: function_f3_default key.led_next_key: function_f3
f3: f3:
key.column_net: GP5 key.column_net: COL4
key.led_previous_key: function_f4_default key.led_next_key: function_f4
f4: f4:
key.column_net: GP4 key.column_net: COL5
key.led_previous_key: function_f5_default key.led_next_key: function_f5
# Block of 4 keys with 6mm offset # Block of 4 keys with gap
f5: f5:
key.spread: cx + 6 key.spread: cx + function_gap
key.column_net: GP3 key.column_net: COL6
key.led_previous_key: function_f6_default key.led_next_key: function_f6
f6: f6:
key.column_net: GP2 key.column_net: COL7
key.led_previous_key: function_f7_default key.led_next_key: function_f7
# Skip matrix column 8 (arrow keys)
f7: f7:
key.column_net: GP1 key.column_net: COL9
key.led_previous_key: function_f8_default key.led_next_key: function_f8
f8: f8:
key.column_net: GP0 key.column_net: COL10
key.led_previous_key: status_led key.led_next_key: function_f9
# Additional points for special components in the upper right corner: Status LED, reset button, rotary encoder # Block of 4 keys with gap
special: f9:
# Align points with the function key zone key.spread: cx + function_gap
key.column_net: COL11
key.led_next_key: function_f10
f10:
key.column_net: COL12
key.led_next_key: function_f11
f11:
key.column_net: COL13
key.led_next_key: function_f12
f12:
key.column_net: COL14
key.led_next_key: main_twelve_numbers
# RP2040 Pico Mini controller board
controller:
anchor: anchor:
ref: primary_ten_numbers # Place in the center of the keyboard, above the arrow keys
shift: [ 0, cy + function_zone_offset ] aggregate.parts:
- main_five_numbers
- main_six_numbers
shift: [ 0, 1.5 ]
# These are not actually keys # This is an auxiliary point to place the controller, not an actual key
key: key:
tags: [ no_key ] tags: [ is_controller ]
width: 18
height: 36
# Define points # Rotary encoder in the center of the keyboard (on top of the controller board)
columns: rotary_center:
# Neopixel status LED (first in the chain, see comment in PCB footprints for details) anchor:
status_led: ref: controller
key: rotate: -90
width: 3.4 key:
height: 3.0 tags: [ is_rotary_encoder ]
width: 2 * rotary_encoder_radius
height: 2 * rotary_encoder_radius
# Matrix position of the switch: between F7 and F8
row_net: ROW6
column_net: COL8
# Reset button # Rotary encoder in the top right corner of the keyboard
reset_button: rotary_top_right:
key: anchor:
shift: [ -0.25cx - 1, 0 ] ref: function_f12
width: 6.5 shift: [ cx + 2 + function_gap, -cy/8 ]
height: 6.5 key:
tags: [ is_rotary_encoder ]
# Rotary encoder in the upper right corner width: 2 * rotary_encoder_radius
rotary: height: 2 * rotary_encoder_radius
key: # Matrix position of the switch: Function row, right of F12
row_net: GP12 row_net: ROW6
column_net: GP22 column_net: COL15
shift: [ -0.25cx, 0 ]
width: 20.5
height: 20.5
tags: [ is_rotary_encoder ]
# Generate outlines that can be used in the PCB and for 3D models # Generate outlines that can be used in the PCB and for 3D models
outlines: outlines:
# Outline of the key caps # Outline of the key caps
keys: keys:
# 1u keys
- what: rectangle - what: rectangle
where: is_key operation: stack
where: [ [ is_key, -1_5u ] ]
size: [ kcx, kcy ] size: [ kcx, kcy ]
# 1.5u keys
- what: rectangle
operation: stack
where: [ [ is_key, 1_5u ] ]
size: [ 1.5kcx, kcy ]
# Rotary encoder with knob (2cm) # Rotary encoder with knob (2cm)
rotary_encoder: rotary_encoder:
@ -299,46 +440,77 @@ outlines:
size: [ 14, 14 ] size: [ 14, 14 ]
- what: circle - what: circle
operation: stack operation: stack
radius: 10 radius: rotary_encoder_radius
# Outline for the PCB # Outline for the PCB
board: board_outline:
- what: polygon - what: polygon
points: points:
- ref: primary_zero_numbers # Top left corner
shift: [ -0.5cx + 1, 1.5cy + function_zone_offset + 2 ] - ref: &corner_top_left_anchor
- ref: primary_twelve_numbers - ref: main_left_numbers
shift: [ 0.5cx - 1, 1.5cy + function_zone_offset + 2] affect: x
- ref: primary_twelve_mods - ref: function_f1
shift: [ 0.5cx - 1, -0.5cy + 2 ] affect: y
- ref: mirror_thumb_four - shift: [ -0.5kcx, 0.5kcy ]
shift: [ 0.5cx, -0.5cy + 2 ] # Extend all corners 4mm to the sides to get a 4mm border
- ref: thumb_four shift: [ -4, 4 ]
shift: [ 0.5cx, -0.5cy + 2 ]
- ref: primary_zero_mods # Top right corner
shift: [ -0.5cx + 1, -0.5cy + 2 ] - ref: &corner_top_right_anchor
expand: 4 - ref: main_twelve_numbers
affect: x
- ref: function_f12
affect: y
- shift: [ 0.5kcx, 0.5kcy ]
shift: [ 4, 4 ]
# Bottom right corner
- ref:
- ref: main_twelve_mods
shift: [ 0.5kcx, -0.5kcy ]
shift: [ 4, -4 ]
# Bottom center-right corner (below thumb keys)
- ref:
- ref: mirror_thumb_four
shift: [ 0.5kcx, -0.5kcy ]
# Discard the rotation, so the next shift moves the corner straight down
affect: xy
shift: [ 0, -4 ]
# Bottom center-left corner (below thumb keys)
- ref:
- ref: thumb_four
shift: [ 0.5kcx, -0.5kcy ]
# Discard the rotation, so the next shift moves the corner straight down
affect: xy
shift: [ 0, -4 ]
# Bottom left corner
- ref:
- ref: main_left_mods
shift: [ -0.5kcx, -0.5kcy ]
shift: [ -4, -4 ]
# Round off the corners with an 8mm radius fillet.
# NOTE: To get a smoother arc on the bottom of the board, do some post-processing in KiCad:
# 1. Remove the bottom center line and the two small fillet arcs next to it.
# 2. Select the two remaining bottom lines. Right click them and use "Shape Modification -> Extend Lines to Meet".
# 3. Right click the two angled lines again. Use "Shape Modification -> Fillet Lines..." with a 200mm radius.
fillet: 8
# Preview version of board with key caps and components for visualization # Preview version of board with key caps and components for visualization
board_preview: board_preview:
- board - board_outline
- ^keys - ^keys
# RP2040 controller board
# RP2040 Pico Mini controller board
- what: rectangle - what: rectangle
operation: stack operation: stack
where: primary_eleven_numbers where: is_controller
size: [ 54, 20 ] size: [ 18, 36 ]
adjust.shift: [ 0, cy + function_zone_offset ]
# Status LED
- what: rectangle
operation: stack
where: special_status_led
size: [ 3.4, 3.0 ]
# Reset button
- what: rectangle
operation: stack
where: special_reset_button
size: [ 6.5, 6.5 ]
# Rotary encoder # Rotary encoder
- what: outline - what: outline
name: rotary_encoder name: rotary_encoder
@ -348,12 +520,12 @@ outlines:
# Cutouts for the switches # Cutouts for the switches
switch_cutouts: switch_cutouts:
- what: rectangle - what: rectangle
where: true where: is_key
size: 14 size: 14
# Board outline with switch cutouts # Board outline with switch cutouts
switch_plate: switch_plate:
- board - board_outline
- -switch_cutouts - -switch_cutouts
# Generate the PCB # Generate the PCB
@ -362,20 +534,48 @@ pcbs:
# Define outline (edges) of the board based on the outlines defined above # Define outline (edges) of the board based on the outlines defined above
outlines: outlines:
main: main:
outline: board outline: board_outline
# Define PCB components # Define PCB components
footprints: footprints:
controller: controller:
what: rp2040_purple what: rp2040_pico_mini
where: where: is_controller
ref: primary_eleven_numbers
shift: [ 0, cy + function_zone_offset ]
rotate: 90
params: params:
# Mount the controller board on the backside of the PCB
orientation: down orientation: down
# Use VBUS (5V from USB) as VCC net # Use 5V from USB as VCC net
VBUS: VCC 5V: VCC
# RGB LEDs: The chain starts with the Esc key
GP29: led_din_function_esc
# Matrix columns
GP27: COL1
GP26: COL2
GP25: COL3
GP23: COL4
GP22: COL5
GP21: COL6
GP20: COL7
GP17: COL8 # Center column (arrow keys)
GP12: COL9
GP11: COL10
GP10: COL11
GP9: COL12
GP8: COL13
GP7: COL14
GP6: COL15
# Matrix rows
GP13: ROW1
GP14: ROW2
GP19: ROW3
GP18: ROW4
GP15: ROW5
GP28: ROW6
# Rotary encoders
GP0: rotary_top_right_a
GP1: rotary_top_right_b
GP3: rotary_center_a
GP2: rotary_center_b
choc_hotswap: choc_hotswap:
what: choc_pretty what: choc_pretty
@ -385,10 +585,10 @@ pcbs:
params: params:
keycaps: true keycaps: true
hotswap: true hotswap: true
from: "{{colrow}}" from: "diode_{{name}}"
to: "{{column_net}}" to: "{{column_net}}"
diode: key_diode:
what: diode_smd what: diode_smd
where: is_key where: is_key
adjust: adjust:
@ -397,21 +597,21 @@ pcbs:
resist: true resist: true
params: params:
side: B side: B
from: "{{colrow}}" from: "diode_{{name}}"
to: "{{row_net}}" to: "{{row_net}}"
rotary_encoder: rotary_encoder:
what: rotary_modified what: rotary_modified
where: is_rotary_encoder where: is_rotary_encoder
params: params:
from: "{{colrow}}" from: "diode_{{name}}"
to: "{{column_net}}" to: "{{column_net}}"
A: GP10 A: "{{name}}_a"
B: GP11 B: "{{name}}_b"
C: GND C: GND
diode_rotary_encoder: rotary_encoder_diode:
$extends: pcbs.eepyboard.footprints.diode $extends: pcbs.eepyboard.footprints.key_diode
where: is_rotary_encoder where: is_rotary_encoder
adjust: adjust:
shift: [ 0, 0 ] shift: [ 0, 0 ]
@ -426,8 +626,9 @@ pcbs:
shift: [ 0, 4.7 ] shift: [ 0, 4.7 ]
params: params:
side: B side: B
din: "led_dout_{{led_previous_key}}" din: "led_din_{{name}}"
dout: "led_dout_{{zone.name}}_{{colrow}}" dout: "led_din_{{led_next_key}}"
VCC: "{{led_vcc_net}}"
led_capacitor: led_capacitor:
what: cap_0805 what: cap_0805
@ -438,12 +639,12 @@ pcbs:
resist: true resist: true
params: params:
side: B side: B
from: VCC from: "{{led_vcc_net}}"
to: GND to: GND
led_chip_flipped: led_chip_flipped:
$extends: pcbs.eepyboard.footprints.led_chip $extends: pcbs.eepyboard.footprints.led_chip
# Place flipped (rotated by 180°) LEDs in all rows *with* the flip_led tag # Place flipped (rotated by 180°) LEDs in all rows with the flip_led tag
where: [ [ is_key, flip_led ] ] where: [ [ is_key, flip_led ] ]
adjust.rotate: 180 adjust.rotate: 180
@ -455,93 +656,82 @@ pcbs:
rotate: 270 rotate: 270
resist: true resist: true
# Additional first LED (not bound to a key) that serves as a status LED. # The first SK6812 chip in the chain has to run on a reduced voltage to serve as a makeshift level converter for
# Additionally, it runs on a reduced voltage to serve as a level converter for the next LED: The RP2040 runs on # all following RGB LEDs.
# 3.3V, but the SK6812 runs on 5V and requires at least 3.5V (5V * 0.7) for the control signal. By reducing the # This is needed because the RP2040 runs on 3.3V, but the SK6812 runs on 5V and requires at least 3.5V (5V * 0.7)
# power voltage with a diode, the threshold is reduced as well, so that the signal of the RP2040 is strong enough. # for the control signal. By reducing the power voltage with a diode, the threshold is reduced as well, so that
status_led_chip: # the signal of the RP2040 is strong enough.
what: sk6812_mini_e first_led_voltage_diode:
where: special_status_led
params:
side: B
VCC: VCC_status_led
din: GP9
dout: led_dout_status_led
status_led_capacitor:
what: cap_0805
where: special_status_led
adjust:
shift: [ -6, -0.2 ]
rotate: 90
params:
side: B
from: VCC_status_led
to: GND
# Diode to reduce the voltage of the first LED chip by ~0.7V
status_led_diode:
what: diode_smd what: diode_smd
where: special_status_led where: function_esc
adjust: adjust:
shift: [ 0, 3.5 ] shift: [ 9.5, 2.25 ]
rotate: 0 rotate: -90
params: params:
side: B side: B
from: VCC from: VCC
to: VCC_status_led to: VCC_first_led
reset_button: # Mounting holes: Top left and right corners
what: button_6x6
where: special_reset_button
params:
from: GND
to: RUN
# Mounting holes
mounting_hole_top_left: mounting_hole_top_left:
what: mountinghole_m2 what: mountinghole_m2
where: where:
ref: function_esc ref: *corner_top_left_anchor
shift: [ -9, 11.5 ] shift: [ 2, -2 ]
mounting_hole_top_center:
what: mountinghole_m2
where:
ref: function_f4
shift: [ 12.5, 0 ]
mounting_hole_top_right: mounting_hole_top_right:
what: mountinghole_m2 what: mountinghole_m2
where: where:
ref: primary_twelve_numbers ref: *corner_top_right_anchor
shift: [ 9, cy + function_zone_offset + 11.5 ] shift: [ -2, -2 ]
# Mounting holes: Top center, left and right of the controller
mounting_hole_controller_left:
what: mountinghole_m2
where:
ref: main_five_numbers
shift: [ 0, 12 ]
mounting_hole_controller_right:
what: mountinghole_m2
where:
ref: main_six_numbers
shift: [ 0, 12 ]
# Mounting holes: Bottom center, under columns five/six (but above the thumb keys)
mounting_hole_bottom_center_left:
what: mountinghole_m2
where:
ref: main_five_bottom
shift: [ 0, -13 ]
mounting_hole_bottom_center_right:
what: mountinghole_m2
where:
ref: main_six_bottom
shift: [ 0, -13 ]
# Mounting holes: Bottom left and right corners, under the second key columns
mounting_hole_bottom_left: mounting_hole_bottom_left:
what: mountinghole_m2 what: mountinghole_m2
where: where:
ref: primary_one_mods ref: main_zero_mods
shift: [ 10.5, -12 ] shift: [ 0, -13.5 ]
mounting_hole_bottom_center:
what: mountinghole_m2
where:
ref: primary_six_bottom
shift: [ 0, -23 ]
mounting_hole_bottom_right: mounting_hole_bottom_right:
what: mountinghole_m2 what: mountinghole_m2
where: where:
ref: primary_eleven_mods ref: main_eleven_mods
shift: [ -10.5, -12 ] shift: [ 0, -13.5 ]
# Render text with project name and copyright onto the PCB # Render text with project name and copyright onto the PCB
copyright_text: copyright_text:
what: text what: text
where: where:
ref: primary_six_bottom ref: thumb_one
shift: [ 0, -15 ] shift: [ 0, -16.5 ]
rotate: -0.3 # total rotation: -6.3 (including key rotation)
params: params:
text: |- text: |-
eepyBoard v1.0 by binaryDiv eepyBoard v1.1 by binaryDiv
(c) 2024 (MIT License) (c) 2024 (MIT License)

View File

@ -0,0 +1,182 @@
// Pin header footprint for a small RP2040-based controller board from AliExpress called "Pico Mini RP2040".
// It has a similar footprint to the Pro Micro (NOT pin-compatible though!), but has an additional row of pins
// on the short edge of the board (basically forming a "U").
// -------------------------------
// Params
// orientation: if up (default), the controller will be on the front side of the PCB, otherwise on the back side
module.exports = {
params: {
designator: 'MCU',
orientation: 'up',
// Left side of pins (with USB port at the top), top to bottom
GP0: {type: 'net', value: 'GP0'},
GP1: {type: 'net', value: 'GP1'},
GP2: {type: 'net', value: 'GP2'},
GP3: {type: 'net', value: 'GP3'},
GP4: {type: 'net', value: 'GP4'},
GP5: {type: 'net', value: 'GP5'},
GP6: {type: 'net', value: 'GP6'},
GP7: {type: 'net', value: 'GP7'},
GP8: {type: 'net', value: 'GP8'},
GP9: {type: 'net', value: 'GP9'},
GP10: {type: 'net', value: 'GP10'},
GP11: {type: 'net', value: 'GP11'},
GP12: {type: 'net', value: 'GP12'},
GP13: {type: 'net', value: 'GP13'},
// Bottom side of pins, left to right (i.e. between GP13 and GP19)
GP14: {type: 'net', value: 'GP14'},
GP15: {type: 'net', value: 'GP15'},
GP16: {type: 'net', value: 'GP16'},
GP17: {type: 'net', value: 'GP17'},
GP18: {type: 'net', value: 'GP18'},
// Right side of pins (with USB port at the top), top to bottom
'5V': {type: 'net', value: '5V'}, // 5V USB voltage
GND: {type: 'net', value: 'GND'},
RST: {type: 'net', value: 'RST'}, // Reset pin: Pull down to reset
'3V3': {type: 'net', value: '3V3'}, // 3.3V voltage used by the MCU
GP29: {type: 'net', value: 'GP29'}, // also: ADC4
GP28: {type: 'net', value: 'GP28'}, // also: ADC2
GP27: {type: 'net', value: 'GP27'}, // also: ADC1
GP26: {type: 'net', value: 'GP26'}, // also: ADC0
GP25: {type: 'net', value: 'GP25'},
GP23: {type: 'net', value: 'GP23'},
GP22: {type: 'net', value: 'GP22'},
GP21: {type: 'net', value: 'GP21'},
GP20: {type: 'net', value: 'GP20'},
GP19: {type: 'net', value: 'GP19'},
},
body: p => {
let sign, side, mirror;
if (p.orientation === 'down') {
sign = -1;
side = 'B';
mirror = 'mirror';
} else {
sign = 1;
side = 'F';
mirror = '';
}
const pins_left = [
'GP0', 'GP1', 'GP2', 'GP3', 'GP4', 'GP5', 'GP6', 'GP7', 'GP8', 'GP9', 'GP10', 'GP11', 'GP12', 'GP13',
];
const pins_bottom = [
'GP14', 'GP15', 'GP16', 'GP17', 'GP18',
];
const pins_right = [
'5V', 'GND', 'RST', '3V3', 'GP29', 'GP28', 'GP27', 'GP26', 'GP25', 'GP23', 'GP22', 'GP21', 'GP20', 'GP19',
];
// Functions to map coordinates from a "pin grid" to actual X/Y coordinates.
// The grid is defined as follows (assuming the board is in "up" orientation with the USB port on top):
// - The X coordinates go from 0 (left) to 6 (right)
// - The Y coordinates go from 0 (top) to 13 (bottom)
// This means the pin rows are on these coordinates:
// - Left side of pins: 0/0 to 0/13
// - Right side of pins: 6/0 to 6/13
// - Additional pins on the bottom (not counting the left and right pins): 1/13 to 5/13
// These grid coordinates have to be mapped to footprint coordinates so that the board is centered, meaning
// the grid position 3/6.5 needs to be mapped to footprint coordinates 0/0. The grid is a standard 2.54mm grid.
function pin_pos_x(pin_number) {
// Use integers to avoid floating point issues
return sign * (254 * (pin_number - 3)) / 100;
}
function pin_pos_y(pin_number) {
// Use integers to avoid floating point issues
return (254 * (pin_number - 7) + 127) / 100;
}
function pin_label(pin_name, pos_x, pos_y, pin_side) {
let offset_x = 0;
let offset_y = 0;
let offset_rotation = 0;
let justify = 'left';
if (pin_side === 'left') {
offset_x = -1.3 * sign;
justify = 'right';
} else if (pin_side === 'right') {
offset_x = 1.3 * sign;
} else if (pin_side === 'bottom') {
offset_y = 1.7;
offset_rotation = -90 * sign;
}
// Shorten the name of the "GPxx" pins to save a bit of space on the PCB
if (pin_name.startsWith('GP')) {
pin_name = pin_name.slice(2);
}
return `
(fp_text user ${pin_name}
(at ${pin_pos_x(pos_x) + offset_x} ${pin_pos_y(pos_y) + offset_y} ${p.r + offset_rotation})
(unlocked yes) (layer ${side}.SilkS)
(effects (font (size 0.8 0.8) (thickness 0.15)) (justify ${justify} ${mirror}))
)`;
}
function pin_pad(pad_number, pos_x, pos_y, pin_name) {
return `
(pad ${pad_number} thru_hole circle
(at ${pin_pos_x(pos_x)} ${pin_pos_y(pos_y)} 0)
(size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.Mask)
${p[pin_name]}
)`;
}
function render_pins() {
let pin_label_body = '';
let pin_pad_body = '';
let pad_number = 1;
for (let i = 0; i < pins_left.length; i++) {
pin_label_body += pin_label(pins_left[i], 0, i, 'left');
pin_pad_body += pin_pad(pad_number++, 0, i, pins_left[i]);
}
for (let i = 0; i < pins_bottom.length; i++) {
pin_label_body += pin_label(pins_bottom[i], 1 + i, 13, 'bottom');
pin_pad_body += pin_pad(pad_number++, 1 + i, 13, pins_bottom[i]);
}
for (let i = 0; i < pins_right.length; i++) {
pin_label_body += pin_label(pins_right[i], 6, i, 'right');
pin_pad_body += pin_pad(pad_number++, 6, i, pins_right[i]);
}
return pin_pad_body + '\n' + pin_label_body;
}
return `
(module RP2040_PICO_MINI (layer F.Cu) (tedit 66FD3C6D)
${p.at /* parametric position */}
${'' /* footprint reference */}
(fp_text reference "${p.ref}" (at 0 0) (layer ${side}.SilkS) ${p.ref_hide}
(effects (font (size 1.27 1.27) (thickness 0.15)))
)
(fp_text value "" (at 0 0) (layer ${side}.SilkS) hide
(effects (font (size 1.27 1.27) (thickness 0.15)))
)
${'' /* component outline */}
(fp_line (start -8.8 -18.0) (end 8.8 -18.0) (layer ${side}.SilkS) (width 0.15))
(fp_line (start 8.8 -18.0) (end 8.8 18.0) (layer ${side}.SilkS) (width 0.15))
(fp_line (start 8.8 18.0) (end -8.8 18.0) (layer ${side}.SilkS) (width 0.15))
(fp_line (start -8.8 18.0) (end -8.8 -18.0) (layer ${side}.SilkS) (width 0.15))
${'' /* illustration of the USB port overhang */}
(fp_line (start -4.3 -18.8) (end 4.3 -18.8) (layer Dwgs.User) (width 0.15))
(fp_line (start 4.3 -18.8) (end 4.3 -11.8) (layer Dwgs.User) (width 0.15))
(fp_line (start 4.3 -11.8) (end -4.3 -11.8) (layer Dwgs.User) (width 0.15))
(fp_line (start -4.3 -11.8) (end -4.3 -18.8) (layer Dwgs.User) (width 0.15))
${'' /* pin labels and pads */}
${render_pins()}
)
`;
}
}

View File

@ -1,140 +0,0 @@
// Pin header footprint for a RP2040-based controller board from AliExpress.
// Doesn't really have a name, it's just "the purple one with USB-C".
// The pinout differs from the Raspberry Pi Pico!
// -------------------------------
// Params
// orientation: if up (default), the controller will be on the front side of the PCB, otherwise on the back side
module.exports = {
params: {
designator: 'MCU',
orientation: 'up',
// Right side of pins (with USB port at the top), top to bottom
VBUS: {type: 'net', value: 'VBUS'},
VIN: {type: 'net', value: 'VIN'},
GND: {type: 'net', value: 'GND'},
'3V3_EN': {type: 'net', value: '3V3_EN'},
'3V3': {type: 'net', value: '3V3'},
// (GND pin)
RUN: {type: 'net', value: 'RUN'}, // Reset pin: Pull down to reset
GP29: {type: 'net', value: 'GP29'}, // also: ADC3
GP28: {type: 'net', value: 'GP28'}, // also: ADC2
GP27: {type: 'net', value: 'GP27'}, // also: ADC1
GP26: {type: 'net', value: 'GP26'}, // also: ADC0
AGND: {type: 'net', value: 'AGND'},
GP25: {type: 'net', value: 'GP25'},
GP24: {type: 'net', value: 'GP24'},
GP23: {type: 'net', value: 'GP23'},
GP22: {type: 'net', value: 'GP22'},
GP21: {type: 'net', value: 'GP21'},
GP20: {type: 'net', value: 'GP20'},
GP19: {type: 'net', value: 'GP19'},
GP18: {type: 'net', value: 'GP18'},
// Left side of pins (with USB port at the top), top to bottom
GP0: {type: 'net', value: 'GP0'},
GP1: {type: 'net', value: 'GP1'},
GP2: {type: 'net', value: 'GP2'},
GP3: {type: 'net', value: 'GP3'},
GP4: {type: 'net', value: 'GP4'},
// (GND pin)
GP5: {type: 'net', value: 'GP5'},
GP6: {type: 'net', value: 'GP6'},
GP7: {type: 'net', value: 'GP7'},
GP8: {type: 'net', value: 'GP8'},
GP9: {type: 'net', value: 'GP9'},
GP10: {type: 'net', value: 'GP10'},
GP11: {type: 'net', value: 'GP11'},
GP12: {type: 'net', value: 'GP12'},
// (GND pin)
GP13: {type: 'net', value: 'GP13'},
GP14: {type: 'net', value: 'GP14'},
GP15: {type: 'net', value: 'GP15'},
GP16: {type: 'net', value: 'GP16'},
GP17: {type: 'net', value: 'GP17'},
},
body: p => {
let def_neg, def_pos, side, mirror;
if (p.orientation === 'down') {
def_neg = '';
def_pos = '-';
side = 'B';
mirror = 'mirror';
} else {
def_neg = '-';
def_pos = '';
side = 'F';
mirror = '';
}
const PINS_PER_SIDE = 20;
const PINS_PER_SIDE_HALF = 10;
const pins_left = [
'GP0', 'GP1', 'GP2', 'GP3', 'GP4', 'GND', 'GP5', 'GP6', 'GP7', 'GP8',
'GP9', 'GP10', 'GP11', 'GP12', 'GND', 'GP13', 'GP14', 'GP15', 'GP16', 'GP17',
];
const pins_right = [
'VBUS', 'VIN', 'GND', '3V3_EN', '3V3', 'GND', 'RUN', 'GP29', 'GP28', 'GP27',
'GP26', 'AGND', 'GP25', 'GP24', 'GP23', 'GP22', 'GP21', 'GP20', 'GP19', 'GP18',
];
function pin_pos_y(pin_number) {
// Fucking floating points...
return (254 * (pin_number - PINS_PER_SIDE_HALF) + 127) / 100;
}
function pin_labels(pin_names, right_side) {
const sign = right_side ? def_pos : def_neg;
const justify = right_side ? 'right' : 'left';
let pin_label_body = '';
for (let i = 0; i < PINS_PER_SIDE; i++) {
pin_label_body += `
(fp_text user ${pin_names[i]} (at ${sign}7.62 ${pin_pos_y(i)} ${p.r}) (unlocked yes) (layer ${side}.SilkS)
(effects (font (size 0.8 0.8) (thickness 0.15)) (justify ${justify} ${mirror}))
)`;
}
return pin_label_body;
}
function pin_pads(pin_names, right_side) {
const sign = right_side ? def_pos : def_neg;
const offset = right_side ? 21 : 1;
let pin_pad_body = '';
for (let i = 0; i < PINS_PER_SIDE; i++) {
pin_pad_body += `
(pad ${i + offset} thru_hole circle (at ${sign}8.89 ${pin_pos_y(i)} 0)
(size 1.7526 1.7526) (drill 1.0922) (layers *.Cu *.Mask) ${p[pin_names[i]]}
)`;
}
return pin_pad_body;
}
return `
(module RP2040_USB_C_Purple (layer F.Cu) (tedit 66A6C4CD)
${p.at /* parametric position */}
${'' /* footprint reference */}
(fp_text reference "${p.ref}" (at 0 0) (layer ${side}.SilkS) ${p.ref_hide} (effects (font (size 1.27 1.27) (thickness 0.15))))
(fp_text value "" (at 0 0) (layer ${side}.SilkS) hide (effects (font (size 1.27 1.27) (thickness 0.15))))
${'' /* component outline */}
(fp_line (start -10.16 -28.6) (end 10.16 -28.6) (layer ${side}.SilkS) (width 0.15))
(fp_line (start 10.16 -28.6) (end 10.16 25.4) (layer ${side}.SilkS) (width 0.15))
(fp_line (start 10.16 25.4) (end -10.16 25.4) (layer ${side}.SilkS) (width 0.15))
(fp_line (start -10.16 25.4) (end -10.16 -28.6) (layer ${side}.SilkS) (width 0.15))
${'' /* pin labels */}
${pin_labels(pins_left, false)}
${pin_labels(pins_right, true)}
${'' /* pin pads */}
${pin_pads(pins_left, false)}
${pin_pads(pins_right, true)}
)
`;
}
}