├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml └── workflows │ └── firmware.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── bootloader └── openocd.cfg ├── firmware ├── _board │ ├── board_eclair-m0.go │ ├── eclair-m0.json │ └── eclair-m0.ld ├── apps │ ├── bootloader │ │ └── main.go │ ├── charmap │ │ └── main.go │ ├── config.go │ ├── flashlight │ │ └── main.go │ ├── launcher │ │ ├── config.go │ │ └── main.go │ ├── mouse │ │ └── main.go │ └── notes │ │ ├── icons.go │ │ ├── keymap.go │ │ ├── main.go │ │ └── note.go ├── go.mod ├── go.sum ├── godoc.sh ├── hal │ ├── battery │ │ └── main.go │ ├── brownout │ │ └── main.go │ ├── clocks │ │ └── main.go │ ├── display │ │ ├── font.go │ │ ├── main.go │ │ └── renderer.go │ ├── keypad │ │ ├── codes.go │ │ └── main.go │ ├── reset │ │ └── main.go │ └── watchdog │ │ └── main.go ├── main.go └── storage │ └── main.go ├── hardware ├── .gitignore ├── board │ ├── fp-lib-table │ ├── main.kicad_pcb │ ├── main.kicad_pro │ ├── main.kicad_sch │ ├── main.kicad_sym │ ├── main.pretty │ │ ├── BatteryHolder_MPD_BC2003_1x2032.kicad_mod │ │ ├── MountingHole_2.2mm_M2_Pad_Small.kicad_mod │ │ ├── REX012832.kicad_mod │ │ ├── SW_SPDT_PCM12.kicad_mod │ │ ├── SW_SPST_EVQQ2.kicad_mod │ │ └── USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal.kicad_mod │ └── sym-lib-table ├── enclosure-text │ ├── main.kicad_pcb │ ├── main.kicad_pro │ └── output │ │ └── millproject └── enclosure │ ├── _base.scad │ ├── bottom.scad │ ├── slide.scad │ └── top.scad └── tools ├── convert.py ├── icons.pbm ├── icons.txt ├── newstroke.pbm └── newstroke.txt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | --- 5 | 6 | > **READ THIS FIRST** 7 | > 8 | > Please note that EclairM0 is mostly complete, and no significant changes are planned. Software fixes for crashes and compatibility issues are more than welcome. Hardware fixes may not be merged because I have no way to confirm that everything still works as expected. 9 | > 10 | > This is a hobby project. Therefore, I reserve the right to not respond to issues because of a lack of time/interest. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/workflows/firmware.yml: -------------------------------------------------------------------------------- 1 | name: Build UF2 firmware 2 | env: 3 | TINYGO_ROOT: /usr/local/lib/tinygo 4 | TINYGO_VERSION: "0.37.0" 5 | on: 6 | push: 7 | tags: 8 | - "*" 9 | permissions: 10 | contents: write 11 | jobs: 12 | build: 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: firmware/go.mod 19 | - name: Install TinyGo 20 | run: | 21 | sudo apt-get update && sudo apt-get install wget -y 22 | wget -nv https://github.com/tinygo-org/tinygo/releases/download/v${TINYGO_VERSION}/tinygo_${TINYGO_VERSION}_amd64.deb 23 | sudo apt-get install ./tinygo_${TINYGO_VERSION}_amd64.deb -y 24 | rm tinygo_${TINYGO_VERSION}_amd64.deb 25 | - name: Add custom board definition 26 | working-directory: firmware 27 | run: | 28 | sudo cp _board/board_eclair-m0.go ${TINYGO_ROOT}/src/machine/ 29 | sudo cp _board/eclair-m0.json ${TINYGO_ROOT}/targets/ 30 | sudo cp _board/eclair-m0.ld ${TINYGO_ROOT}/targets/ 31 | - name: Build UF2 file 32 | working-directory: firmware 33 | run: | 34 | tinygo build -target eclair-m0 -size full -o eclair-firmware-${GITHUB_REF_NAME}-$(date +%Y%m%d).uf2 35 | - name: Create draft release 36 | working-directory: firmware 37 | run: | 38 | gh release create ${GITHUB_REF_NAME} --draft *.uf2 39 | env: 40 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## Software (MIT) 2 | 3 | Copyright 2024-2025 Mateusz Nowak. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | ## Hardware (CERN-OHL-W v2) 12 | 13 | Copyright 2024-2025 Mateusz Nowak. 14 | 15 | This Source describes Open Hardware and is licensed under the CERN-OHL-W v2. 16 | 17 | You may redistribute and modify this Source and make products using it under the [terms of the CERN-OHL-W v2](https://ohwr.org/cern_ohl_w_v2.txt). 18 | 19 | This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-W v2 for applicable conditions. 20 | 21 | Source Location: https://mateusznowak.dev/eclair, https://github.com/mateusznowakdev/eclair 22 | 23 | As per CERN-OHL-W v2 section 4.1, should You produce hardware based on this Source, You must maintain the Source Location visible on the external case and/or on the printed circuit board of EclairM0 or other products You make using this Source. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EclairM0 2 | 3 | [Project description](https://mateusznowak.dev/eclair) and [build instructions](https://mateusznowak.dev/eclair/build) can be found on my website. 4 | 5 | Hardware files can be previewed in these third-party web applications: 6 | 7 | - KiCad files (board) -- https://kicanvas.org 8 | - OpenSCAD files (enclosure) -- https://openscad.cloud/openscad/ 9 | 10 | License terms for software and hardware are present in the [LICENSE.md](./LICENSE.md) file. 11 | 12 | Jump to: 13 | 14 | - [building firmware from source](#building-firmware-from-source) 15 | - [updating font data](#updating-font-data) 16 | - [3D-printing the enclosure](#3d-printing-the-enclosure) 17 | - [engraving button labels](#engraving-button-labels) 18 | - [changelog](#changelog) 19 | 20 | --- 21 | 22 | ## Building firmware from source 23 | 24 | Install the TinyGo SDK. Copy EclairM0 board definition files as follows: 25 | 26 | | File from this repo | Target SDK directory | 27 | |------------------------------------|----------------------| 28 | | firmware/_board/board_eclair-m0.go | src/machine | 29 | | firmware/_board/eclair-m0.json | targets | 30 | | firmware/_board/eclair-m0.ld | targets | 31 | 32 | Now you should be able to build and upload new firmware, like this: 33 | 34 | ```bash 35 | cd firmware 36 | tinygo flash -target eclair-m0 -size short 37 | ``` 38 | 39 | ## Updating font data 40 | 41 | Font data is stored in the `tools/newstroke.pbm` image file, with `tools/newstroke.txt` containing font widths. The image file can be edited in GIMP. 42 | 43 | These source files can be converted into a working Go code, using the `tools/convert.py` script, which should work with any modern version of Python: 44 | 45 | ```bash 46 | cd tools 47 | python convert.py newstroke.pbm newstroke.txt > ../firmware/hal/display/font.go 48 | ``` 49 | 50 | ## 3D-printing the enclosure 51 | 52 | Creality Ender-3 V3 KE is my 3D printer. I'm using OrcaSlicer 2.2.0, and at this point I'm too scared to update :) 53 | 54 | These are the printing settings recommended by me: 55 | 56 | - Global filament settings (PLA) → 215°C nozzle, 60°C bed 57 | - "0.12mm Fine" base preset 58 | - Quality → Seam position: BACK 59 | - Quality → Scarf joint seam: CONTOUR 60 | - Quality → Elephant foot compensation: 0.15mm (depends on the printer calibration, default is 0.05mm) 61 | - Quality → Precise wall: ON 62 | - Quality → Walls printing order: INNER/OUTER 63 | - Quality → Detect overhang walls: OFF 64 | - Strength → Bottom surface pattern: ARCHIMEDEAN or HILBERT for best appearance 65 | - Speed → First layer: 20mm/s (default is 50mm/s and it's too fast) 66 | - Speed → Top surface: 100mm/s (default is 250mm/s) 67 | 68 | Supports should not be necessary for the bridge under the slide switch cutout. 69 | 70 | I'm printing the parts face down, with the USB cutout pointing to the right. The very center of the heatbed may not give the best results. 71 | 72 | ## Engraving button labels 73 | 74 | Gerber files generated by KiCad can be converted to a G-code compatible with many desktop CNCs. If you happen to use `pcb2gcode`, the example config is available in the repo. 75 | 76 | ## Changelog 77 | 78 | ### Software 79 | 80 | Version 1.1: 81 | 82 | - Added Shift key, and the menu for inserting symbols and some keyboard keys 83 | - Changed font to NewStroke 84 | - Changed USB VID/PID and manufacturer/device name 85 | - Changed battery voltage measurement code 86 | - **Replace resistors before updating -- see Rev.D changelog below** 87 | 88 | Version 1.0: 89 | 90 | - Initial version 91 | 92 | ### Hardware 93 | 94 | Rev.D: 95 | 96 | - Switched to LIR2025(H) battery 97 | - Changed R51 and R52 resistors, to 22K and 5.1K respectively, to have 1V max output 98 | 99 | Rev.C: 100 | 101 | - Added ADC/DAC test point, and extra soldering pad for attaching the unmodified Li-Po battery 102 | 103 | Rev.B: 104 | 105 | - Added Schottky diode to prevent battery from back-powering the USB host 106 | - Added 5V, 3.3V and extra GND test points 107 | 108 | Rev.A: 109 | 110 | - Initial revision 111 | -------------------------------------------------------------------------------- /bootloader/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/raspberrypi-native.cfg] 2 | transport select swd 3 | 4 | set CHIPNAME at91samd21e18 5 | source [find target/at91samdXX.cfg] 6 | 7 | adapter gpio swclk 25 8 | adapter gpio swdio 24 9 | 10 | adapter srst delay 100 11 | adapter srst pulse_width 100 12 | 13 | init 14 | targets 15 | reset halt 16 | at91samd bootloader 0 17 | program bootloader-trinket_m0-v3.16.0.bin verify 18 | at91samd bootloader 8192 19 | reset 20 | shutdown 21 | -------------------------------------------------------------------------------- /firmware/_board/board_eclair-m0.go: -------------------------------------------------------------------------------- 1 | //go:build sam && atsamd21 && eclair_m0 2 | 3 | package machine 4 | 5 | // used to reset into bootloader 6 | const resetMagicValue = 0xf01669ef 7 | 8 | // USBCDC pins 9 | const ( 10 | USBCDC_DM_PIN = PA24 11 | USBCDC_DP_PIN = PA25 12 | ) 13 | 14 | // UART pins 15 | const ( 16 | UART_TX_PIN = NoPin 17 | UART_RX_PIN = NoPin 18 | ) 19 | 20 | // SPI pins 21 | const ( 22 | SPI0_SCK_PIN = PA09 23 | SPI0_SDO_PIN = PA11 24 | SPI0_SDI_PIN = PA08 25 | ) 26 | 27 | // SPI on the Eclair M0 28 | var SPI0 = sercomSPIM0 29 | 30 | // I2C pins 31 | const ( 32 | SDA_PIN = NoPin 33 | SCL_PIN = NoPin 34 | ) 35 | 36 | // I2S pins 37 | const ( 38 | I2S_SCK_PIN = NoPin 39 | I2S_SDO_PIN = NoPin 40 | I2S_SDI_PIN = NoPin 41 | I2S_WS_PIN = NoPin 42 | ) 43 | 44 | // Display pins 45 | const ( 46 | DISP_DC_PIN = PA07 47 | DISP_CS_PIN = PA05 48 | DISP_RST_PIN = PA06 49 | ) 50 | 51 | // Keypad pins 52 | const ( 53 | KEYS_COL1_PIN = PA23 54 | KEYS_COL2_PIN = PA22 55 | KEYS_COL3_PIN = PA19 56 | KEYS_COL4_PIN = PA18 57 | KEYS_COL5_PIN = PA01 58 | KEYS_ROW1_PIN = PA04 59 | KEYS_ROW2_PIN = PA28 60 | KEYS_ROW3_PIN = PA16 61 | ) 62 | 63 | // Voltage measurement pins 64 | const VMETER_PIN = PA02 65 | 66 | // USB CDC identifiers 67 | const ( 68 | usb_STRING_PRODUCT = "EclairM0" 69 | usb_STRING_MANUFACTURER = "Mateusz Nowak" 70 | ) 71 | 72 | var ( 73 | usb_VID uint16 = 0x1209 74 | usb_PID uint16 = 0xEC1A 75 | ) 76 | -------------------------------------------------------------------------------- /firmware/_board/eclair-m0.json: -------------------------------------------------------------------------------- 1 | { 2 | "inherits": ["atsamd21e18a"], 3 | "build-tags": ["eclair_m0"], 4 | "linkerscript": "targets/eclair-m0.ld", 5 | "serial": "usb", 6 | "serial-port": ["1209:EC1A"], 7 | "flash-1200-bps-reset": "true", 8 | "flash-method": "msd", 9 | "msd-volume-name": ["ECLAIRBOOT", "TRINKETBOOT"], 10 | "msd-firmware-name": "firmware.uf2" 11 | } 12 | -------------------------------------------------------------------------------- /firmware/_board/eclair-m0.ld: -------------------------------------------------------------------------------- 1 | INCLUDE "targets/atsamd21.ld" 2 | 3 | /* Use fixed starting point for user data (highest 32kB) */ 4 | __flash_data_start = 0x38000; 5 | -------------------------------------------------------------------------------- /firmware/apps/bootloader/main.go: -------------------------------------------------------------------------------- 1 | package bootloader 2 | 3 | import "machine" 4 | 5 | func Run() { 6 | machine.EnterBootloader() 7 | 8 | //goland:noinspection ALL 9 | for { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /firmware/apps/charmap/main.go: -------------------------------------------------------------------------------- 1 | package charmap 2 | 3 | import ( 4 | "eclair/hal/display" 5 | "eclair/hal/keypad" 6 | "eclair/hal/watchdog" 7 | ) 8 | 9 | var chars = [][]byte{ 10 | {'.', ',', ';', ':', '\'', '"', '-', '+'}, 11 | {'=', '?', '!', '@', '#', '$', '%', '^'}, 12 | {'&', '*', '(', ')', '[', ']', '<', '>'}, 13 | {'{', '}', '\\', '/', '|', '_', '~', '`'}, 14 | {0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87}, 15 | } 16 | 17 | // there is no support for horizontal text centering, and having one would 18 | // slow down the main text renderer, so for now have this offset map instead 19 | var offs = [][]uint8{ 20 | {5, 5, 5, 5, 5, 3, 2, 2}, 21 | {2, 3, 5, 1, 1, 2, 2, 3}, 22 | {2, 2, 3, 3, 4, 4, 3, 3}, 23 | {2, 3, 2, 3, 5, 2, 2, 3}, 24 | {0, 0, 0, 0, 0, 0, 0, 0}, 25 | } 26 | 27 | func refreshDisplay(disp *display.Display, posX int, posY int) { 28 | disp.ClearBufferTop() 29 | 30 | for charNo, char := range chars[posY] { 31 | disp.DrawText([]byte{char}, 0, uint(charNo)*16+uint(offs[posY][charNo])) 32 | } 33 | 34 | disp.DrawTextFrame(0, uint(posX*16), uint(posX*16+14)) 35 | disp.Display() 36 | } 37 | 38 | func wrap(value int, min int, max int) int { 39 | if value < min { 40 | return max 41 | } 42 | if value > max { 43 | return min 44 | } 45 | return value 46 | } 47 | 48 | func Run(disp *display.Display) int { 49 | posX := 0 50 | posY := 0 51 | 52 | result := 0 53 | 54 | // - using existing display instance to prevent it from being reset - 55 | 56 | // - keypad - 57 | 58 | keys := keypad.New() 59 | 60 | keys.SetHandlers([]func(keypad.EventType){ 61 | func(et keypad.EventType) { 62 | if et.Alt() && et.Released() { 63 | result = -1 64 | } 65 | }, 66 | nil, 67 | func(et keypad.EventType) { 68 | if et.Released() { 69 | posY = wrap(posY-1, 0, len(chars)-1) 70 | refreshDisplay(disp, posX, posY) 71 | } 72 | }, 73 | nil, 74 | nil, 75 | nil, 76 | func(et keypad.EventType) { 77 | if et.Released() { 78 | posX = wrap(posX-1, 0, len(chars[0])-1) 79 | refreshDisplay(disp, posX, posY) 80 | } 81 | }, 82 | func(et keypad.EventType) { 83 | if et.Released() { 84 | posY = wrap(posY+1, 0, len(chars)-1) 85 | refreshDisplay(disp, posX, posY) 86 | } 87 | }, 88 | func(et keypad.EventType) { 89 | if et.Released() { 90 | posX = wrap(posX+1, 0, len(chars[0])-1) 91 | refreshDisplay(disp, posX, posY) 92 | } 93 | }, 94 | func(et keypad.EventType) { 95 | if et.Released() { 96 | result = int(chars[posY][posX]) 97 | } 98 | }, 99 | nil, 100 | nil, 101 | nil, 102 | nil, 103 | nil, 104 | }) 105 | 106 | // - main loop - 107 | 108 | refreshDisplay(disp, posX, posY) 109 | 110 | for result == 0 { 111 | watchdog.Feed() 112 | keys.Scan() 113 | } 114 | 115 | return result 116 | } 117 | -------------------------------------------------------------------------------- /firmware/apps/config.go: -------------------------------------------------------------------------------- 1 | package apps 2 | 3 | import "eclair/storage" 4 | 5 | var Bounds = map[string]storage.Bounds{ 6 | "notes": {0, 32}, 7 | } 8 | -------------------------------------------------------------------------------- /firmware/apps/flashlight/main.go: -------------------------------------------------------------------------------- 1 | package flashlight 2 | 3 | import ( 4 | "eclair/hal/display" 5 | "eclair/hal/keypad" 6 | "eclair/hal/reset" 7 | "eclair/hal/watchdog" 8 | ) 9 | 10 | func Run() { 11 | // - display - 12 | 13 | disp := display.New() 14 | 15 | disp.ClearBuffer() 16 | disp.DrawText([]byte("- hold any key -"), 1, 0) 17 | disp.Display() 18 | 19 | disp.SetContrast(display.ContrastLow) 20 | 21 | // - keypad - 22 | 23 | handler := func(et keypad.EventType) { 24 | if et.Pressed() { 25 | disp.SetContrast(display.ContrastHigh) 26 | disp.SetInverted(true) 27 | } else { 28 | disp.SetContrast(display.ContrastLow) 29 | disp.SetInverted(false) 30 | } 31 | } 32 | 33 | keys := keypad.New() 34 | 35 | keys.SetHandlers([]func(keypad.EventType){ 36 | func(et keypad.EventType) { 37 | if et.Alt() && et.Released() { 38 | reset.SoftReset() 39 | } 40 | handler(et) 41 | }, 42 | handler, 43 | handler, 44 | handler, 45 | handler, 46 | handler, 47 | handler, 48 | handler, 49 | handler, 50 | handler, 51 | handler, 52 | handler, 53 | handler, 54 | handler, 55 | nil, 56 | }) 57 | 58 | // - main loop - 59 | 60 | for { 61 | watchdog.Feed() 62 | keys.Scan() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /firmware/apps/launcher/config.go: -------------------------------------------------------------------------------- 1 | package launcher 2 | 3 | import ( 4 | "eclair/apps/bootloader" 5 | "eclair/apps/flashlight" 6 | "eclair/apps/mouse" 7 | "eclair/apps/notes" 8 | ) 9 | 10 | var apps = []Entry{ 11 | {"notes", notes.Run}, 12 | {"mouse", mouse.Run}, 13 | {"flashlight", flashlight.Run}, 14 | {"bootloader", bootloader.Run}, 15 | } 16 | -------------------------------------------------------------------------------- /firmware/apps/launcher/main.go: -------------------------------------------------------------------------------- 1 | package launcher 2 | 3 | import ( 4 | "time" 5 | 6 | "eclair/hal/display" 7 | "eclair/hal/keypad" 8 | "eclair/hal/watchdog" 9 | ) 10 | 11 | type Entry struct { 12 | name string 13 | entrypoint func() 14 | } 15 | 16 | func clamp(value int, min int, max int) int { 17 | if value < min { 18 | return min 19 | } 20 | if value > max { 21 | return max 22 | } 23 | return value 24 | } 25 | 26 | func refreshDisplay(disp display.Display, pos int) { 27 | disp.ClearBuffer() 28 | 29 | opt := apps[pos] 30 | disp.DrawText([]byte(opt.name), 0, 2) 31 | disp.DrawTextFrame(0, 0, 126) 32 | 33 | if pos < len(apps)-1 { 34 | opt = apps[pos+1] 35 | disp.DrawText([]byte(opt.name), 2, 2) 36 | } 37 | 38 | disp.Display() 39 | } 40 | 41 | func Run() { 42 | pos := 0 43 | 44 | // - display - 45 | 46 | disp := display.New() 47 | 48 | // - keypad - 49 | 50 | keys := keypad.New() 51 | 52 | keys.SetHandlers([]func(keypad.EventType){ 53 | nil, 54 | nil, 55 | func(et keypad.EventType) { 56 | if et.Released() { 57 | pos = clamp(pos-1, 0, len(apps)-1) 58 | refreshDisplay(disp, pos) 59 | } 60 | }, 61 | nil, 62 | nil, 63 | nil, 64 | nil, 65 | func(et keypad.EventType) { 66 | if et.Released() { 67 | pos = clamp(pos+1, 0, len(apps)-1) 68 | refreshDisplay(disp, pos) 69 | } 70 | }, 71 | nil, 72 | func(et keypad.EventType) { 73 | if et.Released() { 74 | disp.ClearDisplay() 75 | time.Sleep(250 * time.Millisecond) 76 | 77 | apps[pos].entrypoint() 78 | } 79 | }, 80 | nil, 81 | nil, 82 | nil, 83 | nil, 84 | nil, 85 | }) 86 | 87 | // - main loop - 88 | 89 | refreshDisplay(disp, pos) 90 | 91 | for { 92 | watchdog.Feed() 93 | keys.Scan() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /firmware/apps/mouse/main.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import ( 4 | "machine/usb/hid/mouse" 5 | "math" 6 | "time" 7 | 8 | "eclair/hal/display" 9 | "eclair/hal/keypad" 10 | "eclair/hal/reset" 11 | "eclair/hal/watchdog" 12 | ) 13 | 14 | const acceleration = 1.1 15 | const maxSpeed = 8.0 16 | 17 | func Run() { 18 | speedX := float64(0) 19 | speedY := float64(0) 20 | 21 | // - display - 22 | 23 | disp := display.New() 24 | 25 | disp.ClearBuffer() 26 | disp.DrawText([]byte("- mouse mode -"), 1, 2) 27 | disp.Display() 28 | 29 | disp.SetContrast(display.ContrastLow) 30 | 31 | // - keypad - 32 | 33 | keys := keypad.New() 34 | 35 | keys.SetHandlers([]func(keypad.EventType){ 36 | func(et keypad.EventType) { 37 | if et.Alt() && et.Released() { 38 | reset.SoftReset() 39 | } 40 | }, 41 | nil, 42 | func(et keypad.EventType) { 43 | if et.Pressed() { 44 | speedY = -1 45 | } else { 46 | speedY = 0 47 | } 48 | }, 49 | nil, 50 | nil, 51 | nil, 52 | func(et keypad.EventType) { 53 | if et.Pressed() { 54 | speedX = -1 55 | } else { 56 | speedX = 0 57 | } 58 | }, 59 | func(et keypad.EventType) { 60 | if et.Pressed() { 61 | speedY = 1 62 | } else { 63 | speedY = 0 64 | } 65 | }, 66 | func(et keypad.EventType) { 67 | if et.Pressed() { 68 | speedX = 1 69 | } else { 70 | speedX = 0 71 | } 72 | }, 73 | nil, 74 | nil, 75 | func(et keypad.EventType) { 76 | if et.Pressed() { 77 | mouse.Mouse.Press(mouse.Left) 78 | } else { 79 | mouse.Mouse.Release(mouse.Left) 80 | } 81 | }, 82 | func(et keypad.EventType) { 83 | if et.Pressed() { 84 | mouse.Mouse.Press(mouse.Right) 85 | } else { 86 | mouse.Mouse.Release(mouse.Right) 87 | } 88 | }, 89 | nil, 90 | nil, 91 | }) 92 | 93 | // - main loop - 94 | 95 | for { 96 | watchdog.Feed() 97 | keys.Scan() 98 | 99 | mouse.Mouse.Move(int(speedX), int(speedY)) 100 | 101 | if speedX != 0 && math.Abs(speedX) < maxSpeed { 102 | speedX = speedX * acceleration 103 | } 104 | if speedY != 0 && math.Abs(speedY) < maxSpeed { 105 | speedY = speedY * acceleration 106 | } 107 | 108 | time.Sleep(15 * time.Millisecond) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /firmware/apps/notes/icons.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | const iconBattery = 0 4 | const iconFile = 1 5 | const iconShift = 2 6 | 7 | var icons = [][]uint8{ 8 | {0x0E, 0x1B, 0x11, 0x11, 0x11, 0x1F, 0x00, 0x00}, 9 | {0x1F, 0x11, 0x10, 0x13, 0x1B, 0x00, 0x00, 0x00}, 10 | {0x04, 0x06, 0x1F, 0x06, 0x04, 0x00, 0x00, 0x00}, 11 | } 12 | -------------------------------------------------------------------------------- /firmware/apps/notes/keymap.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import "machine/usb/hid/keyboard" 4 | 5 | const Shift = keyboard.KeyLeftShift 6 | 7 | // keymap for US English (international) keyboard 8 | var keymap = [][]keyboard.Keycode{ 9 | /* */ {keyboard.KeySpace}, 10 | /* ! */ {Shift, keyboard.Key1}, 11 | /* " */ {Shift, keyboard.KeyQuote}, 12 | /* # */ {Shift, keyboard.Key3}, 13 | /* $ */ {Shift, keyboard.Key4}, 14 | /* % */ {Shift, keyboard.Key5}, 15 | /* & */ {Shift, keyboard.Key7}, 16 | /* ' */ {keyboard.KeyQuote}, 17 | /* ( */ {Shift, keyboard.Key9}, 18 | /* ) */ {Shift, keyboard.Key0}, 19 | /* * */ {Shift, keyboard.Key8}, 20 | /* + */ {Shift, keyboard.KeyEqual}, 21 | /* , */ {keyboard.KeyComma}, 22 | /* - */ {keyboard.KeyMinus}, 23 | /* . */ {keyboard.KeyPeriod}, 24 | /* / */ {keyboard.KeySlash}, 25 | /* 0 */ {keyboard.Key0}, 26 | /* 1 */ {keyboard.Key1}, 27 | /* 2 */ {keyboard.Key2}, 28 | /* 3 */ {keyboard.Key3}, 29 | /* 4 */ {keyboard.Key4}, 30 | /* 5 */ {keyboard.Key5}, 31 | /* 6 */ {keyboard.Key6}, 32 | /* 7 */ {keyboard.Key7}, 33 | /* 8 */ {keyboard.Key8}, 34 | /* 9 */ {keyboard.Key9}, 35 | /* : */ {Shift, keyboard.KeySemicolon}, 36 | /* ; */ {keyboard.KeySemicolon}, 37 | /* < */ {Shift, keyboard.KeyComma}, 38 | /* = */ {keyboard.KeyEqual}, 39 | /* > */ {Shift, keyboard.KeyPeriod}, 40 | /* ? */ {Shift, keyboard.KeySlash}, 41 | /* @ */ {Shift, keyboard.Key2}, 42 | /* A */ {Shift, keyboard.KeyA}, 43 | /* B */ {Shift, keyboard.KeyB}, 44 | /* C */ {Shift, keyboard.KeyC}, 45 | /* D */ {Shift, keyboard.KeyD}, 46 | /* E */ {Shift, keyboard.KeyE}, 47 | /* F */ {Shift, keyboard.KeyF}, 48 | /* G */ {Shift, keyboard.KeyG}, 49 | /* H */ {Shift, keyboard.KeyH}, 50 | /* I */ {Shift, keyboard.KeyI}, 51 | /* J */ {Shift, keyboard.KeyJ}, 52 | /* K */ {Shift, keyboard.KeyK}, 53 | /* L */ {Shift, keyboard.KeyL}, 54 | /* M */ {Shift, keyboard.KeyM}, 55 | /* N */ {Shift, keyboard.KeyN}, 56 | /* O */ {Shift, keyboard.KeyO}, 57 | /* P */ {Shift, keyboard.KeyP}, 58 | /* Q */ {Shift, keyboard.KeyQ}, 59 | /* R */ {Shift, keyboard.KeyR}, 60 | /* S */ {Shift, keyboard.KeyS}, 61 | /* T */ {Shift, keyboard.KeyT}, 62 | /* U */ {Shift, keyboard.KeyU}, 63 | /* V */ {Shift, keyboard.KeyV}, 64 | /* W */ {Shift, keyboard.KeyW}, 65 | /* X */ {Shift, keyboard.KeyX}, 66 | /* Y */ {Shift, keyboard.KeyY}, 67 | /* Z */ {Shift, keyboard.KeyZ}, 68 | /* [ */ {keyboard.KeyLeftBrace}, 69 | /* \ */ {keyboard.KeyBackslash}, 70 | /* ] */ {keyboard.KeyRightBrace}, 71 | /* ^ */ {Shift, keyboard.Key6}, 72 | /* _ */ {Shift, keyboard.KeyMinus}, 73 | /* ` */ {keyboard.KeyTilde}, 74 | /* a */ {keyboard.KeyA}, 75 | /* b */ {keyboard.KeyB}, 76 | /* c */ {keyboard.KeyC}, 77 | /* d */ {keyboard.KeyD}, 78 | /* e */ {keyboard.KeyE}, 79 | /* f */ {keyboard.KeyF}, 80 | /* g */ {keyboard.KeyG}, 81 | /* h */ {keyboard.KeyH}, 82 | /* i */ {keyboard.KeyI}, 83 | /* j */ {keyboard.KeyJ}, 84 | /* k */ {keyboard.KeyK}, 85 | /* l */ {keyboard.KeyL}, 86 | /* m */ {keyboard.KeyM}, 87 | /* n */ {keyboard.KeyN}, 88 | /* o */ {keyboard.KeyO}, 89 | /* p */ {keyboard.KeyP}, 90 | /* q */ {keyboard.KeyQ}, 91 | /* r */ {keyboard.KeyR}, 92 | /* s */ {keyboard.KeyS}, 93 | /* t */ {keyboard.KeyT}, 94 | /* u */ {keyboard.KeyU}, 95 | /* v */ {keyboard.KeyV}, 96 | /* w */ {keyboard.KeyW}, 97 | /* x */ {keyboard.KeyX}, 98 | /* y */ {keyboard.KeyY}, 99 | /* z */ {keyboard.KeyZ}, 100 | /* { */ {Shift, keyboard.KeyLeftBrace}, 101 | /* | */ {Shift, keyboard.KeyBackslash}, 102 | /* } */ {Shift, keyboard.KeyRightBrace}, 103 | /* ~ */ {Shift, keyboard.KeyTilde}, 104 | /* DEL */ nil, 105 | /* */ {keyboard.KeyLeft}, 106 | /* */ {keyboard.KeyDown}, 107 | /* */ {keyboard.KeyUp}, 108 | /* */ {keyboard.KeyRight}, 109 | /* */ {keyboard.KeyEnter}, 110 | /* */ {keyboard.KeyBackspace}, 111 | /* */ {keyboard.KeyTab}, 112 | /* */ {keyboard.KeyEsc}, 113 | } 114 | -------------------------------------------------------------------------------- /firmware/apps/notes/main.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import ( 4 | "machine/usb/hid/keyboard" 5 | "slices" 6 | "time" 7 | 8 | "eclair/apps/charmap" 9 | "eclair/hal/battery" 10 | "eclair/hal/display" 11 | "eclair/hal/keypad" 12 | "eclair/hal/reset" 13 | "eclair/hal/watchdog" 14 | ) 15 | 16 | func deleteLine(note *Note) { 17 | lines := display.GetLines(note.file.Data) 18 | 19 | lineNo := getCursorLineNumber(lines, note.cursor) 20 | line := lines[lineNo] 21 | 22 | note.file.Data = slices.Delete(note.file.Data, line.Start, line.End) 23 | note.cursor = line.Start 24 | note.markDirty() 25 | } 26 | 27 | func dimScreen(disp *display.Display) { 28 | if disp.Contrast() != display.ContrastLow { 29 | disp.SetContrast(display.ContrastLow) 30 | } else { 31 | disp.SetContrast(display.ContrastNormal) 32 | } 33 | } 34 | 35 | func getCursorLineNumber(lines []display.Line, cursor int) int { 36 | for ln := len(lines) - 1; ln >= 0; ln-- { 37 | line := lines[ln] 38 | if cursor >= line.Start { 39 | return ln 40 | } 41 | } 42 | 43 | return 0 44 | } 45 | 46 | func handler(note *Note, shift bool, et keypad.EventType, alt func(), opts ...byte) bool { 47 | if alt != nil && et.Alt() && et.Released() { 48 | alt() 49 | return true 50 | } 51 | 52 | if et.Double() && et.Released() { 53 | last := upper(note.last(), shift) 54 | 55 | for optNo, opt := range opts { 56 | opt = upper(opt, shift) 57 | if last != opt { 58 | continue 59 | } 60 | 61 | optNo += 1 62 | if optNo == len(opts) { 63 | optNo = 0 64 | } 65 | 66 | note.replace(upper(opts[optNo], shift)) 67 | return true 68 | } 69 | 70 | note.insert(upper(opts[0], shift)) 71 | return true 72 | } 73 | 74 | if et.Released() { 75 | note.insert(upper(opts[0], shift)) 76 | return true 77 | } 78 | 79 | return false 80 | } 81 | 82 | func insertExtra(note *Note, disp *display.Display) { 83 | result := charmap.Run(disp) 84 | if result > 0 { 85 | note.insert(byte(result)) 86 | } 87 | } 88 | 89 | func nextLine(note *Note) { 90 | lines := display.GetLines(note.file.Data) 91 | lineNo := getCursorLineNumber(lines, note.cursor) 92 | 93 | if lineNo < len(lines)-1 { 94 | // go down one line at the time 95 | note.cursor = lines[lineNo+1].Start 96 | } else if note.cursor < len(note.file.Data) { 97 | // then go to the end of a line 98 | note.cursor = len(note.file.Data) 99 | } 100 | } 101 | 102 | func prevLine(note *Note) { 103 | lines := display.GetLines(note.file.Data) 104 | 105 | lineNo := getCursorLineNumber(lines, note.cursor) 106 | line := lines[lineNo] 107 | 108 | if note.cursor > line.Start { 109 | // go to the beginning of a line 110 | note.cursor = line.Start 111 | } else if lineNo > 0 { 112 | // then go up one line at the time 113 | note.cursor = lines[lineNo-1].Start 114 | } 115 | } 116 | 117 | func refreshDisplay(disp display.Display, batt battery.Battery, note Note, shift bool) { 118 | disp.ClearBuffer() 119 | disp.DrawMultiText(note.file.Data, note.cursor) 120 | 121 | if note.dirty() { 122 | icon := icons[iconFile] 123 | disp.DrawSprite(icon, 0, uint(display.Width-len(icon))) 124 | } else if !batt.Good() { 125 | icon := icons[iconBattery] 126 | disp.DrawSprite(icon, 0, uint(display.Width-len(icon))) 127 | } 128 | 129 | if shift { 130 | icon := icons[iconShift] 131 | disp.DrawSprite(icon, 0, 0) 132 | } 133 | 134 | disp.Display() 135 | } 136 | 137 | func saveAndExit(note *Note) { 138 | _, err := note.write() 139 | if err != nil { 140 | panic(err) 141 | } 142 | 143 | reset.SoftReset() 144 | } 145 | 146 | func sendToPC(note *Note) { 147 | for _, char := range note.file.Data { 148 | keys := keymap[char-32] 149 | for _, key := range keys { 150 | keyboard.Keyboard.Down(key) 151 | time.Sleep(10 * time.Millisecond) 152 | } 153 | for _, key := range slices.Backward(keys) { 154 | keyboard.Keyboard.Up(key) 155 | time.Sleep(10 * time.Millisecond) 156 | } 157 | } 158 | } 159 | 160 | func upper(value byte, shift bool) byte { 161 | if shift && value >= 'a' && value <= 'z' { 162 | return value - 32 163 | } 164 | return value 165 | } 166 | 167 | func Run() { 168 | shift := false 169 | 170 | note := NewNote() 171 | _, err := note.read() 172 | if err != nil { 173 | panic(err) 174 | } 175 | 176 | // - display - 177 | 178 | disp := display.New() 179 | 180 | // - keypad - 181 | 182 | keys := keypad.New() 183 | 184 | keys.SetBoolHandlers([]func(keypad.EventType) bool{ 185 | func(et keypad.EventType) bool { 186 | return handler(¬e, shift, et, func() { saveAndExit(¬e) }, 'q', 'w', '1') 187 | }, 188 | func(et keypad.EventType) bool { 189 | return handler(¬e, shift, et, nil, 'e', 'r', '2') 190 | }, 191 | func(et keypad.EventType) bool { 192 | return handler(¬e, shift, et, func() { prevLine(¬e) }, 't', 'y', '3') 193 | }, 194 | func(et keypad.EventType) bool { 195 | return handler(¬e, shift, et, func() { insertExtra(¬e, &disp) }, 'u', 'i', '4') 196 | }, 197 | func(et keypad.EventType) bool { 198 | return handler(¬e, shift, et, func() { note.delete() }, 'o', 'p', '5') 199 | }, 200 | func(et keypad.EventType) bool { 201 | return handler(¬e, shift, et, func() { shift = !shift }, 'a', 's', '6') 202 | }, 203 | func(et keypad.EventType) bool { 204 | return handler(¬e, shift, et, func() { note.cursorLeft() }, 'd', 'f', '7') 205 | }, 206 | func(et keypad.EventType) bool { 207 | return handler(¬e, shift, et, func() { nextLine(¬e) }, 'g', 'h', '8') 208 | }, 209 | func(et keypad.EventType) bool { 210 | return handler(¬e, shift, et, func() { note.cursorRight() }, 'j', 'k', '9') 211 | }, 212 | func(et keypad.EventType) bool { 213 | return handler(¬e, shift, et, func() { sendToPC(¬e) }, 'l', '-', '0') 214 | }, 215 | func(et keypad.EventType) bool { 216 | return handler(¬e, shift, et, nil, 'z', 'x', '!') 217 | }, 218 | func(et keypad.EventType) bool { 219 | return handler(¬e, shift, et, func() { deleteLine(¬e) }, 'c', 'v', '?') 220 | }, 221 | func(et keypad.EventType) bool { 222 | return handler(¬e, shift, et, func() { dimScreen(&disp) }, 'b', 'n', '\'') 223 | }, 224 | func(et keypad.EventType) bool { 225 | return handler(¬e, shift, et, func() { note.insert(' ') }, 'm', '.', ',') 226 | }, 227 | nil, 228 | }) 229 | 230 | // - battery - 231 | 232 | batt := battery.New() 233 | 234 | // - main loop - 235 | 236 | refreshDisplay(disp, batt, note, shift) 237 | 238 | for { 239 | watchdog.Feed() 240 | 241 | changed, err := note.writeDelayed() 242 | if err != nil { 243 | panic(err) 244 | } 245 | 246 | changed = changed || keys.Scan() 247 | if !changed { 248 | continue 249 | } 250 | 251 | refreshDisplay(disp, batt, note, shift) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /firmware/apps/notes/note.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import ( 4 | "slices" 5 | "time" 6 | 7 | "eclair/apps" 8 | "eclair/storage" 9 | ) 10 | 11 | const writeDelay = 1500 // ms 12 | 13 | type Note struct { 14 | file *storage.File 15 | 16 | cursor int 17 | timestamp int64 18 | } 19 | 20 | func NewNote() Note { 21 | bounds := apps.Bounds["notes"] 22 | file := storage.NewFile(bounds) 23 | 24 | return Note{file: file} 25 | } 26 | 27 | func (n *Note) cursorLeft() { 28 | if n.cursor > 0 { 29 | n.cursor -= 1 30 | } 31 | } 32 | 33 | func (n *Note) cursorRight() { 34 | if n.cursor < len(n.file.Data) { 35 | n.cursor += 1 36 | } 37 | } 38 | 39 | func (n *Note) delete() { 40 | if n.cursor > 0 { 41 | n.file.Data = slices.Delete(n.file.Data, n.cursor-1, n.cursor) 42 | n.cursor -= 1 43 | } 44 | 45 | n.markDirty() 46 | } 47 | 48 | func (n *Note) dirty() bool { 49 | return n.timestamp > 0 50 | } 51 | 52 | func (n *Note) insert(b byte) { 53 | if len(n.file.Data) < n.file.MaxSize() { 54 | n.file.Data = slices.Insert(n.file.Data, n.cursor, b) 55 | n.cursor += 1 56 | } 57 | 58 | n.markDirty() 59 | } 60 | 61 | func (n *Note) last() byte { 62 | if n.cursor > 0 { 63 | return n.file.Data[n.cursor-1] 64 | } else { 65 | return 0 66 | } 67 | } 68 | 69 | func (n *Note) markDirty() { 70 | n.timestamp = time.Now().UnixMilli() 71 | } 72 | 73 | func (n *Note) read() (bool, error) { 74 | success, err := n.file.Read() 75 | n.cursor = len(n.file.Data) 76 | 77 | return success, err 78 | } 79 | 80 | func (n *Note) replace(b byte) { 81 | if n.cursor > 0 { 82 | n.file.Data[n.cursor-1] = b 83 | } 84 | 85 | n.markDirty() 86 | } 87 | 88 | func (n *Note) write() (bool, error) { 89 | n.timestamp = 0 90 | return true, n.file.Write() 91 | } 92 | 93 | func (n *Note) writeDelayed() (bool, error) { 94 | if n.timestamp == 0 || n.timestamp+writeDelay > time.Now().UnixMilli() { 95 | return false, nil 96 | } 97 | 98 | return n.write() 99 | } 100 | -------------------------------------------------------------------------------- /firmware/go.mod: -------------------------------------------------------------------------------- 1 | module eclair 2 | 3 | go 1.23 4 | 5 | require tinygo.org/x/drivers v0.31.1-0.20250520104313-d41bc0b85f63 6 | 7 | require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 8 | -------------------------------------------------------------------------------- /firmware/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 2 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 3 | tinygo.org/x/drivers v0.31.1-0.20250520104313-d41bc0b85f63 h1:t6OSYoupDGHQZt4pDJzpQZE4SAqkjzUgzVbqz9HtkLc= 4 | tinygo.org/x/drivers v0.31.1-0.20250520104313-d41bc0b85f63/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk= 5 | -------------------------------------------------------------------------------- /firmware/godoc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | GOROOT=/usr/share/go-1.23 GOPROXY="direct" godoc -http=:8900 & 4 | xdg-open http://localhost:8900/pkg/eclair/ 5 | -------------------------------------------------------------------------------- /firmware/hal/battery/main.go: -------------------------------------------------------------------------------- 1 | package battery 2 | 3 | import ( 4 | "device/sam" 5 | "machine" 6 | ) 7 | 8 | const threshold = 3300 // mV 9 | const hysteresis = 100 // mV 10 | 11 | type Battery struct { 12 | adc machine.ADC 13 | voltage int 14 | good bool 15 | } 16 | 17 | // New creates a new Battery instance and configures the underlying ADC channel 18 | // using 1V fixed reference voltage. 19 | func New() Battery { 20 | machine.InitADC() 21 | 22 | adc := machine.ADC{Pin: machine.VMETER_PIN} 23 | adc.Configure(machine.ADCConfig{}) 24 | 25 | sam.ADC.INPUTCTRL.ReplaceBits(sam.ADC_INPUTCTRL_GAIN_1X, 0xF, sam.ADC_INPUTCTRL_GAIN_Pos) 26 | sam.ADC.REFCTRL.ReplaceBits(sam.ADC_REFCTRL_REFSEL_INT1V, 0xF, sam.ADC_REFCTRL_REFSEL_Pos) 27 | 28 | return Battery{adc: adc} 29 | } 30 | 31 | func (b *Battery) refresh() { 32 | b.voltage = int(b.adc.Get()) * 5250 / 65535 33 | 34 | if b.voltage < threshold { 35 | b.good = false 36 | } else if b.voltage > threshold+hysteresis { 37 | b.good = true 38 | } 39 | } 40 | 41 | // Good reads the current battery voltage, compares it to the previous value 42 | // with hysteresis, and returns the boolean value based on a comparison result. 43 | func (b *Battery) Good() bool { 44 | b.refresh() 45 | return b.good 46 | } 47 | 48 | // Voltage reads the current battery voltage and returns the value in 49 | // millivolts. 50 | func (b *Battery) Voltage() int { 51 | b.refresh() 52 | return b.voltage 53 | } 54 | -------------------------------------------------------------------------------- /firmware/hal/brownout/main.go: -------------------------------------------------------------------------------- 1 | package brownout 2 | 3 | import "device/sam" 4 | 5 | // Configure enables the brown-out detector, which resets the device on a 6 | // significant voltage drop on a 3.3V bus, preventing system failures such as 7 | // invalid flash memory operations. 8 | func Configure() { 9 | // disable peripheral 10 | sam.SYSCTRL.BOD33.ClearBits(sam.SYSCTRL_BOD33_ENABLE) 11 | for !sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_B33SRDY) { 12 | } 13 | 14 | // set minimal system voltage to ~2.9V to match Adafruit uf2-samdx1 bootloader 15 | sam.SYSCTRL.BOD33.Set((39 << sam.SYSCTRL_BOD33_LEVEL_Pos) | (sam.SYSCTRL_BOD33_ACTION_RESET << sam.SYSCTRL_BOD33_ACTION_Pos) | sam.SYSCTRL_BOD33_HYST) 16 | 17 | // enable peripheral 18 | sam.SYSCTRL.BOD33.SetBits(sam.SYSCTRL_BOD33_ENABLE) 19 | for !sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_BOD33RDY) { 20 | } 21 | 22 | // wait for system voltage to be stable enough 23 | for sam.SYSCTRL.PCLKSR.HasBits(sam.SYSCTRL_PCLKSR_BOD33DET) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /firmware/hal/clocks/main.go: -------------------------------------------------------------------------------- 1 | package clocks 2 | 3 | import "device/sam" 4 | 5 | const clockGCLK0 = 8_000_000 // Hz 6 | const clockGCLK0Divider = 48_000_000 / clockGCLK0 7 | 8 | // ConfigureCPU changes the GCLK0 clock frequency to 8MHz. This is the main 9 | // clock and is used for CPU and most peripherals. 10 | // 11 | // This will make the USB peripheral unusable, so ConfigureUSB should be called 12 | // immediately. 13 | func ConfigureCPU() { 14 | sam.GCLK.GENDIV.Set(0 | (clockGCLK0Divider << sam.GCLK_GENDIV_DIV_Pos)) 15 | for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { 16 | } 17 | } 18 | 19 | // ConfigureUSB enables the GCLK5 clock at 48MHz and assigns it to the USB 20 | // peripheral. 21 | func ConfigureUSB() { 22 | sam.GCLK.GENDIV.Set(5 | (1 << sam.GCLK_GENDIV_DIV_Pos)) 23 | sam.GCLK.GENCTRL.Set(5 | (sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) 24 | for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { 25 | } 26 | 27 | sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_USB | (sam.GCLK_CLKCTRL_GEN_GCLK5 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) 28 | } 29 | 30 | // ConfigureWatchdog enables the GCLK8 clock at 1MHz and assigns it to the 31 | // watchdog peripheral. 32 | func ConfigureWatchdog() { 33 | sam.GCLK.GENDIV.Set(8 | (32 << sam.GCLK_GENDIV_DIV_Pos)) 34 | sam.GCLK.GENCTRL.Set(8 | (sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) 35 | for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { 36 | } 37 | 38 | sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_WDT | (sam.GCLK_CLKCTRL_GEN_GCLK8 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) 39 | } 40 | 41 | // PatchedGCLK0Frequency returns a valid frequency for the SPI peripheral, based 42 | // on the custom GCLK0 prescaler value. This workaround is needed because 43 | // machine.CPUFrequency on SAMD21 returns a hardcoded value of 48MHz. 44 | func PatchedGCLK0Frequency(value int) int { 45 | return value * clockGCLK0Divider 46 | } 47 | -------------------------------------------------------------------------------- /firmware/hal/display/font.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | var font = [][]uint16{ 4 | /* */ {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, 5 | /* ! */ {0x0000, 0x0000, 0x18FF, 0x0000}, 6 | /* " */ {0x0000, 0x0000, 0x0018, 0x0007, 0x0000, 0x0018, 0x0007, 0x0000}, 7 | /* # */ {0x0000, 0x0000, 0x0200, 0x1E08, 0x03F8, 0x020F, 0x0208, 0x1E08, 0x03F8, 0x020F, 0x0008, 0x0000}, 8 | /* $ */ {0x0000, 0x0000, 0x0418, 0x0824, 0x0842, 0x1FFF, 0x0842, 0x0482, 0x0304, 0x0000}, 9 | /* % */ {0x0000, 0x0000, 0x1806, 0x0609, 0x0189, 0x0C46, 0x1230, 0x120C, 0x0C03, 0x0000}, 10 | /* & */ {0x0000, 0x0000, 0x0780, 0x085E, 0x1021, 0x1061, 0x1191, 0x120E, 0x0C00, 0x1300, 0x0000}, 11 | /* ' */ {0x0000, 0x0000, 0x0018, 0x0007, 0x0000}, 12 | /* ( */ {0x0000, 0x0000, 0x01F0, 0x060C, 0x0802, 0x1001, 0x0000}, 13 | /* ) */ {0x0000, 0x0000, 0x1001, 0x0802, 0x060C, 0x01F0, 0x0000}, 14 | /* * */ {0x0000, 0x0000, 0x0002, 0x0044, 0x0034, 0x000F, 0x0034, 0x0044, 0x0002, 0x0000}, 15 | /* + */ {0x0000, 0x0000, 0x0100, 0x0100, 0x0100, 0x0FE0, 0x0100, 0x0100, 0x0100, 0x0000}, 16 | /* , */ {0x0000, 0x0000, 0xC000, 0x3800, 0x0000}, 17 | /* - */ {0x0000, 0x0000, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0000}, 18 | /* . */ {0x0000, 0x0000, 0x1800, 0x0000}, 19 | /* / */ {0x0000, 0x0000, 0x1800, 0x0600, 0x0180, 0x0060, 0x0018, 0x0006, 0x0001, 0x0000}, 20 | /* 0 */ {0x0000, 0x0000, 0x07FC, 0x0802, 0x1001, 0x1061, 0x1001, 0x0802, 0x07FC, 0x0000}, 21 | /* 1 */ {0x0000, 0x0000, 0x1008, 0x1004, 0x1002, 0x1FFF, 0x1000, 0x1000, 0x1000, 0x0000}, 22 | /* 2 */ {0x0000, 0x0000, 0x1802, 0x1401, 0x1201, 0x1101, 0x1081, 0x1042, 0x103C, 0x0000}, 23 | /* 3 */ {0x0000, 0x0000, 0x0801, 0x1001, 0x1021, 0x1031, 0x1029, 0x0845, 0x0783, 0x0000}, 24 | /* 4 */ {0x0000, 0x0000, 0x0180, 0x0160, 0x0118, 0x0106, 0x0101, 0x1FE0, 0x0100, 0x0000}, 25 | /* 5 */ {0x0000, 0x0000, 0x083C, 0x1023, 0x1011, 0x1011, 0x1011, 0x0821, 0x07C1, 0x0000}, 26 | /* 6 */ {0x0000, 0x0000, 0x07F0, 0x084C, 0x1022, 0x1021, 0x1021, 0x0841, 0x0780, 0x0000}, 27 | /* 7 */ {0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x1C01, 0x0381, 0x0071, 0x000F, 0x0000}, 28 | /* 8 */ {0x0000, 0x0000, 0x078C, 0x0852, 0x1021, 0x1021, 0x1021, 0x0852, 0x078C, 0x0000}, 29 | /* 9 */ {0x0000, 0x0000, 0x003C, 0x1042, 0x1081, 0x1081, 0x0881, 0x0642, 0x01FC, 0x0000}, 30 | /* : */ {0x0000, 0x0000, 0x1860, 0x0000}, 31 | /* ; */ {0x0000, 0x0000, 0xC000, 0x3860, 0x0000}, 32 | /* < */ {0x0000, 0x0000, 0x0100, 0x0280, 0x0440, 0x0820, 0x1010, 0x0000}, 33 | /* = */ {0x0000, 0x0000, 0x0240, 0x0240, 0x0240, 0x0240, 0x0240, 0x0240, 0x0240, 0x0000}, 34 | /* > */ {0x0000, 0x0000, 0x1010, 0x0820, 0x0440, 0x0280, 0x0100, 0x0000}, 35 | /* ? */ {0x0000, 0x0000, 0x0002, 0x0001, 0x1981, 0x0041, 0x0032, 0x000C, 0x0000}, 36 | /* @ */ {0x0000, 0x0000, 0x01E0, 0x0618, 0x08C4, 0x0924, 0x1212, 0x1212, 0x13F4, 0x0A04, 0x0998, 0x0060, 0x0000}, 37 | /* A */ {0x0000, 0x0000, 0x1C00, 0x03E0, 0x021C, 0x0203, 0x021C, 0x03E0, 0x1C00, 0x0000}, 38 | /* B */ {0x0000, 0x0000, 0x1FFF, 0x1041, 0x1041, 0x1041, 0x1041, 0x08A2, 0x071C, 0x0000}, 39 | /* C */ {0x0000, 0x0000, 0x03F8, 0x0C06, 0x1001, 0x1001, 0x1001, 0x1001, 0x0802, 0x0000}, 40 | /* D */ {0x0000, 0x0000, 0x1FFF, 0x1001, 0x1001, 0x1001, 0x0802, 0x060C, 0x01F0, 0x0000}, 41 | /* E */ {0x0000, 0x0000, 0x1FFF, 0x1041, 0x1041, 0x1041, 0x1041, 0x1001, 0x0000}, 42 | /* F */ {0x0000, 0x0000, 0x1FFF, 0x0041, 0x0041, 0x0041, 0x0001, 0x0001, 0x0000}, 43 | /* G */ {0x0000, 0x0000, 0x03F8, 0x0C06, 0x1001, 0x1001, 0x1081, 0x1081, 0x0F81, 0x0000}, 44 | /* H */ {0x0000, 0x0000, 0x1FFF, 0x0040, 0x0040, 0x0040, 0x0040, 0x0040, 0x1FFF, 0x0000}, 45 | /* I */ {0x0000, 0x0000, 0x1FFF, 0x0000}, 46 | /* J */ {0x0000, 0x0000, 0x1000, 0x1000, 0x1000, 0x0800, 0x07FF, 0x0000}, 47 | /* K */ {0x0000, 0x0000, 0x1FFF, 0x0020, 0x0050, 0x0088, 0x0304, 0x0C02, 0x1001, 0x0000}, 48 | /* L */ {0x0000, 0x0000, 0x1FFF, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x0000}, 49 | /* M */ {0x0000, 0x0000, 0x1FFF, 0x000C, 0x0030, 0x00C0, 0x0030, 0x000C, 0x1FFF, 0x0000}, 50 | /* N */ {0x0000, 0x0000, 0x1FFF, 0x000C, 0x0030, 0x00C0, 0x0300, 0x0C00, 0x1FFF, 0x0000}, 51 | /* O */ {0x0000, 0x0000, 0x07FC, 0x0802, 0x1001, 0x1001, 0x1001, 0x0802, 0x07FC, 0x0000}, 52 | /* P */ {0x0000, 0x0000, 0x1FFF, 0x0041, 0x0041, 0x0041, 0x0041, 0x0022, 0x001C, 0x0000}, 53 | /* Q */ {0x0000, 0x0000, 0x07FC, 0x0802, 0x1401, 0x1401, 0x1801, 0x3002, 0x2FFC, 0x2000, 0x0000}, 54 | /* R */ {0x0000, 0x0000, 0x1FFF, 0x0041, 0x0041, 0x00C1, 0x0341, 0x0C22, 0x101C, 0x0000}, 55 | /* S */ {0x0000, 0x0000, 0x081C, 0x1022, 0x1041, 0x1041, 0x1041, 0x0881, 0x0702, 0x0000}, 56 | /* T */ {0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x1FFF, 0x0001, 0x0001, 0x0001, 0x0000}, 57 | /* U */ {0x0000, 0x0000, 0x07FF, 0x0800, 0x1000, 0x1000, 0x1000, 0x0800, 0x07FF, 0x0000}, 58 | /* V */ {0x0000, 0x0000, 0x0003, 0x001C, 0x00E0, 0x0700, 0x1800, 0x0700, 0x00E0, 0x001C, 0x0003, 0x0000}, 59 | /* W */ {0x0000, 0x0000, 0x0007, 0x0078, 0x0780, 0x1800, 0x0780, 0x0070, 0x0780, 0x1800, 0x0780, 0x0078, 0x0007, 0x0000}, 60 | /* X */ {0x0000, 0x0000, 0x1803, 0x060C, 0x01B0, 0x0040, 0x01B0, 0x060C, 0x1803, 0x0000}, 61 | /* Y */ {0x0000, 0x0000, 0x0003, 0x000C, 0x0030, 0x1FC0, 0x0030, 0x000C, 0x0003, 0x0000}, 62 | /* Z */ {0x0000, 0x0000, 0x1C01, 0x1301, 0x10C1, 0x1031, 0x100D, 0x1003, 0x0000}, 63 | /* [ */ {0x0000, 0x0000, 0x1FFF, 0x1001, 0x1001, 0x0000}, 64 | /* \ */ {0x0000, 0x0000, 0x0001, 0x0006, 0x0018, 0x0060, 0x0180, 0x0600, 0x1800, 0x0000}, 65 | /* ] */ {0x0000, 0x0000, 0x1001, 0x1001, 0x1FFF, 0x0000}, 66 | /* ^ */ {0x0000, 0x0000, 0x0004, 0x0002, 0x0001, 0x0002, 0x0004, 0x0000}, 67 | /* _ */ {0x0000, 0x0000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x0000}, 68 | /* ` */ {0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0000}, 69 | /* a */ {0x0000, 0x0000, 0x0F00, 0x1090, 0x1090, 0x1090, 0x1090, 0x1FE0, 0x0000}, 70 | /* b */ {0x0000, 0x0000, 0x1FFF, 0x1010, 0x1010, 0x1010, 0x0820, 0x07C0, 0x0000}, 71 | /* c */ {0x0000, 0x0000, 0x07C0, 0x0820, 0x1010, 0x1010, 0x1010, 0x1010, 0x0000}, 72 | /* d */ {0x0000, 0x0000, 0x07C0, 0x0820, 0x1010, 0x1010, 0x1010, 0x1FFF, 0x0000}, 73 | /* e */ {0x0000, 0x0000, 0x0FE0, 0x1110, 0x1110, 0x1110, 0x1090, 0x00E0, 0x0000}, 74 | /* f */ {0x0000, 0x0000, 0x0010, 0x0010, 0x1FFE, 0x0011, 0x0011, 0x0000}, 75 | /* g */ {0x0000, 0x0000, 0x03C0, 0x8420, 0x8810, 0x8810, 0x4810, 0x3FF0, 0x0000}, 76 | /* h */ {0x0000, 0x0000, 0x1FFF, 0x0010, 0x0010, 0x0010, 0x0010, 0x1FE0, 0x0000}, 77 | /* i */ {0x0000, 0x0000, 0x1FF3, 0x0000}, 78 | /* j */ {0x0000, 0x8000, 0x8000, 0x7FF3, 0x0000}, 79 | /* k */ {0x0000, 0x0000, 0x1FFF, 0x0080, 0x0140, 0x0620, 0x1810, 0x0000}, 80 | /* l */ {0x0000, 0x0000, 0x0FFF, 0x1000, 0x1000, 0x0000}, 81 | /* m */ {0x0000, 0x0000, 0x1FF0, 0x0010, 0x0010, 0x0010, 0x1FE0, 0x0010, 0x0010, 0x0010, 0x1FE0, 0x0000}, 82 | /* n */ {0x0000, 0x0000, 0x1FF0, 0x0010, 0x0010, 0x0010, 0x0010, 0x1FE0, 0x0000}, 83 | /* o */ {0x0000, 0x0000, 0x07C0, 0x0820, 0x1010, 0x1010, 0x0820, 0x07C0, 0x0000}, 84 | /* p */ {0x0000, 0x0000, 0xFFF0, 0x1010, 0x1010, 0x1010, 0x0820, 0x07C0, 0x0000}, 85 | /* q */ {0x0000, 0x0000, 0x07C0, 0x0820, 0x1010, 0x1010, 0x1010, 0xFFF0, 0x0000}, 86 | /* r */ {0x0000, 0x0000, 0x1FF0, 0x0020, 0x0010, 0x0010, 0x0000}, 87 | /* s */ {0x0000, 0x0000, 0x0860, 0x1090, 0x1110, 0x1110, 0x1210, 0x0C20, 0x0000}, 88 | /* t */ {0x0000, 0x0000, 0x0010, 0x0010, 0x0FFF, 0x1010, 0x1010, 0x1010, 0x0000}, 89 | /* u */ {0x0000, 0x0000, 0x0FF0, 0x1000, 0x1000, 0x1000, 0x1000, 0x1FF0, 0x0000}, 90 | /* v */ {0x0000, 0x0000, 0x0030, 0x01C0, 0x0600, 0x1800, 0x0600, 0x01C0, 0x0030, 0x0000}, 91 | /* w */ {0x0000, 0x0000, 0x0070, 0x0780, 0x1800, 0x0700, 0x00C0, 0x0700, 0x1800, 0x0780, 0x0070, 0x0000}, 92 | /* x */ {0x0000, 0x0000, 0x1010, 0x0C60, 0x0280, 0x0100, 0x0280, 0x0C60, 0x1010, 0x0000}, 93 | /* y */ {0x0000, 0x0000, 0x0030, 0x81C0, 0x8600, 0x7800, 0x0600, 0x01F0, 0x0000}, 94 | /* z */ {0x0000, 0x0000, 0x1810, 0x1610, 0x1110, 0x10D0, 0x1030, 0x0000}, 95 | /* { */ {0x0000, 0x0000, 0x0040, 0x00A0, 0x0F1E, 0x1001, 0x1001, 0x0000}, 96 | /* | */ {0x0000, 0x0000, 0x1F1F, 0x0000}, 97 | /* } */ {0x0000, 0x0000, 0x1001, 0x1001, 0x0F1E, 0x00A0, 0x0040, 0x0000}, 98 | /* ~ */ {0x0000, 0x0000, 0x0080, 0x0040, 0x0040, 0x0080, 0x0080, 0x0040, 0x0000}, 99 | /* DEL */ {0x0000, 0x0000, 0x05F4, 0x0208, 0x0514, 0x04A4, 0x0444, 0x04A4, 0x0514, 0x0208, 0x05F4, 0x0000}, 100 | /* */ {0x0000, 0x0000, 0x0080, 0x0140, 0x0220, 0x0410, 0x0808, 0x1004, 0x1E3C, 0x0220, 0x0220, 0x0220, 0x03E0, 0x0000}, 101 | /* */ {0x0000, 0x0000, 0x00C0, 0x0140, 0x0240, 0x047C, 0x0804, 0x1004, 0x0804, 0x047C, 0x0240, 0x0140, 0x00C0, 0x0000}, 102 | /* */ {0x0000, 0x0000, 0x0180, 0x0140, 0x0120, 0x1F10, 0x1008, 0x1004, 0x1008, 0x1F10, 0x0120, 0x0140, 0x0180, 0x0000}, 103 | /* */ {0x0000, 0x0000, 0x03E0, 0x0220, 0x0220, 0x0220, 0x1E3C, 0x1004, 0x0808, 0x0410, 0x0220, 0x0140, 0x0080, 0x0000}, 104 | /* */ {0x0000, 0x0000, 0x0100, 0x0280, 0x0440, 0x0820, 0x1010, 0x1C70, 0x0440, 0x047C, 0x0404, 0x0404, 0x03FC, 0x0000}, 105 | /* */ {0x0000, 0x0000, 0x0080, 0x0140, 0x0220, 0x0410, 0x0808, 0x0A28, 0x0948, 0x0888, 0x0948, 0x0A28, 0x0DD8, 0x0000}, 106 | /* */ {0x0000, 0x0000, 0x0020, 0x0750, 0x0588, 0x0504, 0x05DC, 0x0550, 0x1DD0, 0x1050, 0x08D0, 0x0570, 0x0200, 0x0000}, 107 | /* */ {0x0000, 0x0000, 0x00FC, 0x0684, 0x0844, 0x0884, 0x1114, 0x10AC, 0x1040, 0x0808, 0x0808, 0x0630, 0x01C0, 0x0000}, 108 | } 109 | -------------------------------------------------------------------------------- /firmware/hal/display/main.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | import ( 4 | "machine" 5 | 6 | "eclair/hal/clocks" 7 | 8 | "tinygo.org/x/drivers/ssd1306" 9 | ) 10 | 11 | const Width = 128 12 | const Height = 32 13 | 14 | const ContrastHigh = 255 15 | const ContrastNormal = 63 16 | const ContrastLow = 0 17 | 18 | type Display struct { 19 | device ssd1306.Device 20 | 21 | contrast uint8 22 | inverted bool 23 | } 24 | 25 | func newSPI() *machine.SPI { 26 | frequency := uint32(clocks.PatchedGCLK0Frequency(8_000_000)) 27 | 28 | spi := machine.SPI0 29 | spi.Configure(machine.SPIConfig{Frequency: frequency}) 30 | 31 | return spi 32 | } 33 | 34 | // New creates a new Display instance, configures a SERCOM bus in SPI mode, and 35 | // sends the initialization sequence to the display. 36 | func New() Display { 37 | spi := newSPI() 38 | 39 | device := ssd1306.NewSPI(spi, machine.DISP_DC_PIN, machine.DISP_RST_PIN, machine.DISP_CS_PIN) 40 | device.Configure(ssd1306.Config{Width: Width, Height: Height}) 41 | 42 | // reduce device brightness and allow for wider brightness range 43 | device.Command(ssd1306.SETPRECHARGE) 44 | device.Command(0) 45 | device.Command(ssd1306.SETVCOMDETECT) 46 | device.Command(0) 47 | 48 | display := Display{device: device} 49 | display.SetContrast(ContrastNormal) 50 | display.SetInverted(false) 51 | 52 | return display 53 | } 54 | 55 | // ClearBuffer clears the display buffer. 56 | func (d *Display) ClearBuffer() { 57 | d.device.ClearBuffer() 58 | } 59 | 60 | // ClearBufferTop clears the top half of display buffer. 61 | func (d *Display) ClearBufferTop() { 62 | buffer := d.device.GetBuffer() 63 | for i := 0; i < len(buffer)/2; i++ { 64 | buffer[i] = 0 65 | } 66 | } 67 | 68 | // ClearDisplay clears the display buffer and sends it to the display. 69 | func (d *Display) ClearDisplay() { 70 | d.device.ClearDisplay() 71 | } 72 | 73 | // Contrast returns the current contrast value. 74 | func (d *Display) Contrast() uint8 { 75 | return d.contrast 76 | } 77 | 78 | // Display sends the buffer data to the display. 79 | func (d *Display) Display() { 80 | d.device.Display() 81 | } 82 | 83 | // Inverted returns the current invert state. 84 | func (d *Display) Inverted() bool { 85 | return d.inverted 86 | } 87 | 88 | // SetContrast changes the contrast value and stores it in the Display instance 89 | // (because the display is in write-only mode). 90 | func (d *Display) SetContrast(value uint8) { 91 | d.device.Command(ssd1306.SETCONTRAST) 92 | d.device.Command(value) 93 | d.contrast = value 94 | } 95 | 96 | // SetInverted changes the invert state and stores it in the Display instance 97 | // (because the display is in write-only mode). 98 | func (d *Display) SetInverted(value bool) { 99 | if value { 100 | d.device.Command(ssd1306.INVERTDISPLAY) 101 | } else { 102 | d.device.Command(ssd1306.NORMALDISPLAY) 103 | } 104 | 105 | d.inverted = value 106 | } 107 | -------------------------------------------------------------------------------- /firmware/hal/display/renderer.go: -------------------------------------------------------------------------------- 1 | package display 2 | 3 | const cursorData = 0x5555 4 | 5 | const maxSpriteLines = Height / 8 6 | const maxTextLines = maxSpriteLines - 1 7 | 8 | type Line struct { 9 | Start int 10 | End int 11 | } 12 | 13 | func drawCursor(buffer []byte, bufferPos uint) { 14 | buffer[bufferPos] = cursorData & 0xFF 15 | buffer[bufferPos+Width] = cursorData >> 8 16 | } 17 | 18 | func drawSprite(sprite []uint8, buffer []byte, line uint, xOffset uint) { 19 | if line >= maxSpriteLines { 20 | return 21 | } 22 | 23 | bufferStart := line * Width 24 | 25 | for _, col := range sprite { 26 | if xOffset >= Width { 27 | break 28 | } 29 | 30 | buffer[bufferStart+xOffset] = col 31 | xOffset += 1 32 | } 33 | } 34 | 35 | func drawText(text []byte, cursor int, buffer []byte, line uint, xOffset uint) { 36 | if line >= maxTextLines { 37 | return 38 | } 39 | 40 | bufferStart := line * Width 41 | 42 | for charNo, char := range text { 43 | glyph := getGlyph(char) 44 | 45 | for colNo, col := range glyph { 46 | if xOffset >= Width { 47 | break 48 | } 49 | 50 | if charNo == cursor && colNo == 0 { 51 | col |= cursorData 52 | } 53 | 54 | buffer[bufferStart+xOffset] = uint8(col >> 0) 55 | buffer[bufferStart+Width+xOffset] = uint8(col >> 8) 56 | 57 | xOffset += 1 58 | } 59 | } 60 | 61 | if cursor == len(text) { 62 | if len(text) > 0 { 63 | xOffset -= 1 64 | } 65 | drawCursor(buffer, bufferStart+xOffset) 66 | } 67 | } 68 | 69 | func drawTextFrame(buffer []byte, line uint, x1 uint, x2 uint) { 70 | if line >= maxTextLines { 71 | return 72 | } 73 | 74 | bufferStart := line * Width 75 | 76 | for x := x1; x <= min(x2, Width); x++ { 77 | if x%2 != 0 { 78 | buffer[bufferStart+Width+x] |= 0x80 79 | } 80 | } 81 | 82 | buffer[bufferStart+Width+x1] = 0x50 83 | if x2 < Width { 84 | buffer[bufferStart+Width+x2] = 0x50 85 | } 86 | } 87 | 88 | func getGlyph(char byte) []uint16 { 89 | first := 0x20 90 | missing := 0x7F 91 | 92 | idx := int(char) - first 93 | if idx < 0 || idx >= len(font) { 94 | idx = missing - first 95 | } 96 | 97 | glyph := font[idx] 98 | return glyph 99 | } 100 | 101 | // DrawMultiText fills the entire display buffer with the defined text. 102 | // 103 | // This function splits the text into multiple lines that can fit horizontally 104 | // on the screen, and then scrolls it so the cursor is always displayed on the 105 | // bottom line. 106 | // 107 | // If cursor is set to a negative value, it is not displayed. 108 | func (d *Display) DrawMultiText(text []byte, cursor int) { 109 | lines := GetLines(text) 110 | 111 | // remove lines until the cursor is on the bottom line 112 | for ln := len(lines) - 1; ln > 0; ln-- { 113 | if cursor < lines[ln].Start { 114 | lines = lines[:ln] 115 | } 116 | } 117 | 118 | // making code more readable by storing this in a variable 119 | ll := len(lines) 120 | 121 | if ll >= 1 { 122 | line := lines[ll-1] 123 | drawText(text[line.Start:line.End], cursor-line.Start, d.device.GetBuffer(), 2, 0) 124 | 125 | if ll >= 2 { 126 | line = lines[ll-2] 127 | drawText(text[line.Start:line.End], -1, d.device.GetBuffer(), 0, 0) 128 | } 129 | } 130 | } 131 | 132 | // DrawSprite copies a sprite data to the display buffer at given position. 133 | // 134 | // The line is a value between 0 (top) and 3 (bottom of the screen). 135 | func (d *Display) DrawSprite(sprite []uint8, line uint, xOffset uint) { 136 | drawSprite(sprite, d.device.GetBuffer(), line, xOffset) 137 | } 138 | 139 | // DrawText copies the defined text to the display buffer at given X offset. 140 | // 141 | // The line is a value between 0 (top) and 2 (bottom of the screen). 142 | func (d *Display) DrawText(text []byte, line uint, xOffset uint) { 143 | drawText(text, -1, d.device.GetBuffer(), line, xOffset) 144 | } 145 | 146 | // DrawTextFrame adds a thin frame around text between X1 and X2 points. 147 | // 148 | // The line is a value between 0 (top) and 2 (bottom of the screen). 149 | func (d *Display) DrawTextFrame(line uint, x1 uint, x2 uint) { 150 | drawTextFrame(d.device.GetBuffer(), line, x1, x2) 151 | } 152 | 153 | // GetLines splits the defined text into multiple lines and returns the starting 154 | // and ending points for each line. This function is exported so it can be used 155 | // directly by apps, not only within the text renderer. 156 | func GetLines(text []byte) []Line { 157 | // avoid re-allocations by creating larger backing array ahead of time 158 | lines := make([]Line, 0, len(text)/16) 159 | lines = append(lines, Line{Start: 0}) 160 | 161 | // inner function, it can update the variable directly 162 | updateLines := func(end int, start int) { 163 | lines[len(lines)-1].End = end 164 | lines = append(lines, Line{Start: start}) 165 | } 166 | 167 | lineLen := 0 168 | 169 | wordStart := 0 170 | wordLen := 0 171 | 172 | brokeLongWord := false 173 | 174 | for charNo, char := range text { 175 | glyphWidth := len(getGlyph(char)) 176 | wordLen += glyphWidth 177 | 178 | // handle whitespace between words 179 | if text[charNo] == ' ' { 180 | newLineLen := lineLen + wordLen 181 | 182 | if brokeLongWord || newLineLen-glyphWidth > Width { 183 | lineLen = wordLen 184 | brokeLongWord = false 185 | updateLines(wordStart, wordStart) 186 | } else if newLineLen > Width { 187 | lineLen = 0 188 | updateLines(charNo, charNo+1) 189 | } else { 190 | lineLen += wordLen 191 | } 192 | 193 | wordStart = charNo + 1 194 | wordLen = 0 195 | } 196 | 197 | // handle very long words 198 | if wordLen > Width { 199 | if wordStart > 0 { 200 | updateLines(wordStart, wordStart) 201 | } 202 | 203 | lineLen = glyphWidth 204 | 205 | wordStart = charNo 206 | wordLen = glyphWidth 207 | 208 | brokeLongWord = true 209 | } 210 | } 211 | 212 | // handle last word 213 | newLineLen := lineLen + wordLen 214 | if brokeLongWord || newLineLen > Width { 215 | updateLines(wordStart, wordStart) 216 | } 217 | 218 | // add final line 219 | lines[len(lines)-1].End = len(text) 220 | 221 | return lines 222 | } 223 | -------------------------------------------------------------------------------- /firmware/hal/keypad/codes.go: -------------------------------------------------------------------------------- 1 | package keypad 2 | 3 | type Code int 4 | 5 | //goland:noinspection ALL 6 | const ( 7 | QW Code = iota 8 | ER 9 | TY 10 | UI 11 | OP 12 | AS 13 | DF 14 | GH 15 | JK 16 | L 17 | ZX 18 | CV 19 | BN 20 | M 21 | Unused 22 | ) 23 | -------------------------------------------------------------------------------- /firmware/hal/keypad/main.go: -------------------------------------------------------------------------------- 1 | package keypad 2 | 3 | import ( 4 | "machine" 5 | "time" 6 | ) 7 | 8 | const debounce = 30 // ms 9 | const doubleTap = 1000 // ms 10 | 11 | const codeAlt = ZX 12 | 13 | type Event struct { 14 | code Code 15 | pressed bool 16 | timestamp int64 17 | } 18 | 19 | type EventType int 20 | 21 | const ( 22 | Pressed EventType = 1 << iota 23 | Double 24 | Alt 25 | ) 26 | 27 | func (et EventType) Pressed() bool { 28 | return et&Pressed > 0 29 | } 30 | 31 | func (et EventType) Released() bool { 32 | return et&Pressed == 0 33 | } 34 | 35 | func (et EventType) Single() bool { 36 | return et&Double == 0 37 | } 38 | 39 | func (et EventType) Double() bool { 40 | return et&Double > 0 41 | } 42 | 43 | func (et EventType) Alt() bool { 44 | return et&Alt > 0 45 | } 46 | 47 | func (et EventType) NoAlt() bool { 48 | return et&Alt == 0 49 | } 50 | 51 | type State struct { 52 | pressed bool 53 | debouncing bool 54 | timestamp int64 55 | } 56 | 57 | type Keypad struct { 58 | rows []machine.Pin 59 | cols []machine.Pin 60 | 61 | handlers []func(EventType) bool 62 | 63 | state []State 64 | alt bool 65 | e2 *Event 66 | e1 *Event 67 | } 68 | 69 | // New creates a new Keypad instance, and configures pins of a keyboard matrix. 70 | func New() Keypad { 71 | cols := []machine.Pin{machine.KEYS_COL1_PIN, machine.KEYS_COL2_PIN, machine.KEYS_COL3_PIN, machine.KEYS_COL4_PIN, machine.KEYS_COL5_PIN} 72 | rows := []machine.Pin{machine.KEYS_ROW1_PIN, machine.KEYS_ROW2_PIN, machine.KEYS_ROW3_PIN} 73 | 74 | for _, row := range rows { 75 | row.Configure(machine.PinConfig{Mode: machine.PinInput}) 76 | } 77 | for _, col := range cols { 78 | col.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) 79 | } 80 | 81 | keypad := Keypad{rows: rows, cols: cols, handlers: nil} 82 | keypad.clearState() 83 | 84 | return keypad 85 | } 86 | 87 | func (k *Keypad) scan() *Event { 88 | var event *Event 89 | 90 | id := 0 91 | timestamp := time.Now().UnixMilli() 92 | 93 | for _, row := range k.rows { 94 | row.Configure(machine.PinConfig{Mode: machine.PinOutput}) 95 | row.Low() 96 | 97 | for _, col := range k.cols { 98 | state := &k.state[id] 99 | pressed := !col.Get() 100 | 101 | if pressed == state.pressed { 102 | if state.debouncing { 103 | // stop debouncing (false positive) 104 | state.debouncing = false 105 | state.timestamp = 0 106 | } 107 | } else { 108 | if state.debouncing { 109 | if timestamp >= state.timestamp+debounce { 110 | // stop debouncing 111 | state.pressed = !state.pressed 112 | state.debouncing = false 113 | state.timestamp = 0 114 | event = &Event{code: Code(id), pressed: state.pressed, timestamp: timestamp} 115 | break 116 | } 117 | } else { 118 | // start debouncing 119 | state.debouncing = true 120 | state.timestamp = timestamp 121 | } 122 | } 123 | 124 | id += 1 125 | } 126 | 127 | row.Configure(machine.PinConfig{Mode: machine.PinInput}) 128 | 129 | if event != nil { 130 | break // exit early 131 | } 132 | } 133 | 134 | return event 135 | } 136 | 137 | func (k *Keypad) clearState() { 138 | k.state = make([]State, len(k.rows)*len(k.cols)) 139 | k.alt = false 140 | k.e2 = nil 141 | k.e1 = nil 142 | } 143 | 144 | // Scan checks whether a key was pressed or released (one or more times), and 145 | // calls the respective event handler. This is different from the internal 146 | // Keypad.scan function that is responsible for checking the physical state of 147 | // all buttons. 148 | // 149 | // Scan also updates the internal alt value, so the ZX key can act like a Fn key 150 | // on a normal keyboard. Each app should have at least one handler, that will 151 | // call peripherals.SoftReset when EventType.Alt and EventType.Released are 152 | // true. 153 | func (k *Keypad) Scan() bool { 154 | handled := false 155 | e0 := k.scan() 156 | 157 | if k.handlers != nil && e0 != nil { 158 | handler := k.handlers[e0.code] 159 | et := EventType(0) 160 | 161 | switch true { 162 | case k.e2 != nil && !e0.pressed && !k.e2.pressed && e0.code == k.e2.code && e0.timestamp <= k.e2.timestamp+doubleTap: 163 | et = Double // Released 164 | case k.e2 != nil && e0.pressed && k.e2.pressed && e0.code == k.e2.code && e0.timestamp <= k.e2.timestamp+doubleTap: 165 | et = Double | Pressed 166 | case !e0.pressed: 167 | if k.alt && e0.code == codeAlt { 168 | handler = nil 169 | k.alt = false 170 | } 171 | // et = Released 172 | case e0.pressed: 173 | if k.e1 != nil && k.e1.code == codeAlt && k.e1.pressed { 174 | k.alt = true 175 | } 176 | et = Pressed 177 | } 178 | 179 | if k.alt { 180 | et |= Alt 181 | } 182 | 183 | if handler != nil { 184 | handled = handler(et) 185 | } 186 | 187 | k.e2 = k.e1 188 | k.e1 = e0 189 | } 190 | 191 | return handled 192 | } 193 | 194 | // SetHandlers changes the handlers list for a Keypad instance and resets the 195 | // internal state for a Keypad. Ideally, it should be done when the app is idle 196 | // and no key presses are performed. 197 | func (k *Keypad) SetHandlers(handlers []func(EventType)) { 198 | boolHandlers := make([]func(EventType) bool, len(handlers)) 199 | 200 | for i, handler := range handlers { 201 | if handler != nil { 202 | boolHandlers[i] = func(et EventType) bool { 203 | handler(et) 204 | return true 205 | } 206 | } 207 | } 208 | 209 | k.SetBoolHandlers(boolHandlers) 210 | } 211 | 212 | // SetBoolHandlers changes the handlers list for a Keypad instance and resets 213 | // the internal state for a Keypad. Ideally, it should be done when the app is 214 | // idle and no key presses are performed. 215 | // 216 | // Compared to SetHandlers, this function accepts a list of functions returning 217 | // a boolean value, which is useful for conditional updating of app's state. 218 | func (k *Keypad) SetBoolHandlers(handlers []func(EventType) bool) { 219 | k.handlers = handlers 220 | k.clearState() 221 | } 222 | -------------------------------------------------------------------------------- /firmware/hal/reset/main.go: -------------------------------------------------------------------------------- 1 | package reset 2 | 3 | import ( 4 | "device/sam" 5 | "machine" 6 | "time" 7 | ) 8 | 9 | // IsSoftReset checks if the CPU reset was caused by user action such as 10 | // manually triggering the reset or leaving the bootloader mode. 11 | func IsSoftReset() bool { 12 | return sam.PM.GetRCAUSE_SYST() != 0 13 | } 14 | 15 | // SoftReset performs a CPU reset, after a slight delay for user experience 16 | // reasons. 17 | func SoftReset() { 18 | time.Sleep(200 * time.Millisecond) 19 | machine.CPUReset() 20 | } 21 | -------------------------------------------------------------------------------- /firmware/hal/watchdog/main.go: -------------------------------------------------------------------------------- 1 | package watchdog 2 | 3 | import ( 4 | "device/sam" 5 | 6 | "eclair/hal/clocks" 7 | ) 8 | 9 | // Configure enables the watchdog peripheral, which resets the device if timer 10 | // is not reset ("fed") for a defined amount of time. 11 | func Configure() { 12 | clocks.ConfigureWatchdog() 13 | 14 | // power on the WDT peripheral 15 | sam.PM.APBAMASK.SetBits(sam.PM_APBAMASK_WDT_) 16 | 17 | // disable peripheral 18 | sam.WDT.CTRL.ClearBits(sam.WDT_CTRL_ENABLE) 19 | for sam.WDT.STATUS.HasBits(sam.WDT_STATUS_SYNCBUSY) { 20 | } 21 | 22 | // set 4096 cycles (approximately 4 seconds) 23 | sam.WDT.CONFIG.Set(sam.WDT_CONFIG_PER_4K) 24 | 25 | // enable peripheral 26 | sam.WDT.CTRL.SetBits(sam.WDT_CTRL_ENABLE) 27 | for sam.WDT.STATUS.HasBits(sam.WDT_STATUS_SYNCBUSY) { 28 | } 29 | } 30 | 31 | // Feed resets a watchdog timer, to indicate the app is healthy. 32 | func Feed() { 33 | sam.WDT.CLEAR.Set(sam.WDT_CLEAR_CLEAR_KEY) // other values reset the CPU immediately 34 | } 35 | -------------------------------------------------------------------------------- /firmware/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "eclair/apps/launcher" 5 | "eclair/apps/notes" 6 | "eclair/hal/brownout" 7 | "eclair/hal/clocks" 8 | "eclair/hal/reset" 9 | "eclair/hal/watchdog" 10 | ) 11 | 12 | func main() { 13 | brownout.Configure() 14 | watchdog.Configure() 15 | clocks.ConfigureCPU() 16 | clocks.ConfigureUSB() 17 | 18 | if reset.IsSoftReset() { 19 | launcher.Run() 20 | } else { 21 | notes.Run() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /firmware/storage/main.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "bytes" 5 | "machine" 6 | ) 7 | 8 | type Bounds struct { 9 | StartBlock uint 10 | EndBlock uint 11 | } 12 | 13 | type File struct { 14 | Data []byte 15 | 16 | bounds Bounds 17 | block uint 18 | } 19 | 20 | // NewFile creates a new File instance with defined filesystem boundaries, 21 | // defaulting to the first possible block. 22 | // 23 | // It is necessary to call File.Read to read stored data. 24 | func NewFile(bounds Bounds) *File { 25 | return &File{bounds: bounds, block: bounds.StartBlock} 26 | } 27 | 28 | // MaxSize returns the maximum allowed data size for a filesystem block, 29 | // excluding the metadata section. 30 | func (f *File) MaxSize() int { 31 | return int(machine.Flash.EraseBlockSize() - 4) 32 | } 33 | 34 | // Read attempts to read data stored in the flash memory, within defined 35 | // filesystem boundaries. 36 | // 37 | // If a valid block was found, this function will return true. 38 | // 39 | // If there is a critical error, such as reading out of bounds, it will be 40 | // returned. 41 | func (f *File) Read() (bool, error) { 42 | blockSize := uint(machine.Flash.EraseBlockSize()) 43 | 44 | data := make([]byte, blockSize) 45 | 46 | for b := f.bounds.StartBlock; b < f.bounds.EndBlock; b++ { 47 | _, err := machine.Flash.ReadAt(data, int64(b*blockSize)) 48 | if err != nil { 49 | return false, err 50 | } 51 | 52 | if data[blockSize-4] != 'E' || data[blockSize-3] != 'F' || data[blockSize-2] != 0 { 53 | continue 54 | } 55 | 56 | size := min(int(data[blockSize-1]), f.MaxSize()) 57 | 58 | f.Data = data[:size] 59 | f.block = b 60 | 61 | return true, nil 62 | } 63 | 64 | f.Data = make([]byte, 0, blockSize) 65 | f.block = f.bounds.StartBlock 66 | 67 | return false, nil 68 | } 69 | 70 | // Write attempts to write data stored in a File instance, within defined 71 | // filesystem boundaries. 72 | // 73 | // Write tries to do perform a basic wear leveling by writing data to the next 74 | // available flash block, and then it erases old block by overwriting the magic 75 | // number with garbage data. In the worst case scenario, there would be two 76 | // "valid" blocks, and most of the time the older one will take precedence. 77 | // 78 | // If there is a critical error, such as writing out of bounds, it will be 79 | // returned. 80 | func (f *File) Write() error { 81 | blockSize := uint(machine.Flash.EraseBlockSize()) 82 | writeSize := uint(machine.Flash.WriteBlockSize()) 83 | 84 | meta := bytes.Repeat([]byte{0xFF}, int(writeSize)) 85 | meta[len(meta)-4] = 'E' 86 | meta[len(meta)-3] = 'F' 87 | meta[len(meta)-2] = 0 88 | meta[len(meta)-1] = byte(len(f.Data)) 89 | 90 | block := f.block + 1 91 | if block >= f.bounds.EndBlock { 92 | block = f.bounds.StartBlock 93 | } 94 | 95 | err := machine.Flash.EraseBlocks(int64(block), 1) 96 | if err != nil { 97 | return err 98 | } 99 | _, err = machine.Flash.WriteAt(f.Data, int64(block*blockSize)) 100 | if err != nil { 101 | return err 102 | } 103 | _, err = machine.Flash.WriteAt(meta, int64((block+1)*blockSize-writeSize)) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | if f.block != block { 109 | _, err = machine.Flash.WriteAt(make([]byte, writeSize), int64((f.block+1)*blockSize-writeSize)) 110 | if err != nil { 111 | return err 112 | } 113 | } 114 | 115 | f.block = block 116 | 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /hardware/.gitignore: -------------------------------------------------------------------------------- 1 | *-backups 2 | *.bak 3 | *.FCBak 4 | *.kicad_prl 5 | *.lck 6 | fp-info-cache 7 | -------------------------------------------------------------------------------- /hardware/board/fp-lib-table: -------------------------------------------------------------------------------- 1 | (fp_lib_table 2 | (version 7) 3 | (lib (name "main")(type "KiCad")(uri "${KIPRJMOD}/main.pretty")(options "")(descr "")) 4 | ) 5 | -------------------------------------------------------------------------------- /hardware/board/main.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "3dviewports": [], 4 | "design_settings": { 5 | "defaults": { 6 | "apply_defaults_to_fp_fields": false, 7 | "apply_defaults_to_fp_shapes": false, 8 | "apply_defaults_to_fp_text": false, 9 | "board_outline_line_width": 0.05, 10 | "copper_line_width": 0.2, 11 | "copper_text_italic": false, 12 | "copper_text_size_h": 1.5, 13 | "copper_text_size_v": 1.5, 14 | "copper_text_thickness": 0.3, 15 | "copper_text_upright": false, 16 | "courtyard_line_width": 0.05, 17 | "dimension_precision": 4, 18 | "dimension_units": 3, 19 | "dimensions": { 20 | "arrow_length": 1270000, 21 | "extension_offset": 500000, 22 | "keep_text_aligned": true, 23 | "suppress_zeroes": false, 24 | "text_position": 0, 25 | "units_format": 1 26 | }, 27 | "fab_line_width": 0.1, 28 | "fab_text_italic": false, 29 | "fab_text_size_h": 1.0, 30 | "fab_text_size_v": 1.0, 31 | "fab_text_thickness": 0.15, 32 | "fab_text_upright": false, 33 | "other_line_width": 0.1, 34 | "other_text_italic": false, 35 | "other_text_size_h": 1.0, 36 | "other_text_size_v": 1.0, 37 | "other_text_thickness": 0.15, 38 | "other_text_upright": false, 39 | "pads": { 40 | "drill": 0.0, 41 | "height": 1.0, 42 | "width": 1.0 43 | }, 44 | "silk_line_width": 0.1, 45 | "silk_text_italic": false, 46 | "silk_text_size_h": 1.0, 47 | "silk_text_size_v": 1.0, 48 | "silk_text_thickness": 0.1, 49 | "silk_text_upright": false, 50 | "zones": { 51 | "min_clearance": 0.2 52 | } 53 | }, 54 | "diff_pair_dimensions": [ 55 | { 56 | "gap": 0.0, 57 | "via_gap": 0.0, 58 | "width": 0.0 59 | } 60 | ], 61 | "drc_exclusions": [], 62 | "meta": { 63 | "version": 2 64 | }, 65 | "rule_severities": { 66 | "annular_width": "error", 67 | "clearance": "error", 68 | "connection_width": "warning", 69 | "copper_edge_clearance": "error", 70 | "copper_sliver": "warning", 71 | "courtyards_overlap": "error", 72 | "creepage": "error", 73 | "diff_pair_gap_out_of_range": "error", 74 | "diff_pair_uncoupled_length_too_long": "error", 75 | "drill_out_of_range": "error", 76 | "duplicate_footprints": "warning", 77 | "extra_footprint": "warning", 78 | "footprint": "error", 79 | "footprint_filters_mismatch": "ignore", 80 | "footprint_symbol_mismatch": "warning", 81 | "footprint_type_mismatch": "ignore", 82 | "hole_clearance": "error", 83 | "hole_near_hole": "error", 84 | "hole_to_hole": "error", 85 | "holes_co_located": "warning", 86 | "invalid_outline": "error", 87 | "isolated_copper": "warning", 88 | "item_on_disabled_layer": "error", 89 | "items_not_allowed": "error", 90 | "length_out_of_range": "error", 91 | "lib_footprint_issues": "warning", 92 | "lib_footprint_mismatch": "warning", 93 | "malformed_courtyard": "error", 94 | "microvia_drill_out_of_range": "error", 95 | "mirrored_text_on_front_layer": "warning", 96 | "missing_courtyard": "ignore", 97 | "missing_footprint": "warning", 98 | "net_conflict": "warning", 99 | "nonmirrored_text_on_back_layer": "warning", 100 | "npth_inside_courtyard": "ignore", 101 | "padstack": "warning", 102 | "pth_inside_courtyard": "ignore", 103 | "shorting_items": "error", 104 | "silk_edge_clearance": "warning", 105 | "silk_over_copper": "warning", 106 | "silk_overlap": "warning", 107 | "skew_out_of_range": "error", 108 | "solder_mask_bridge": "error", 109 | "starved_thermal": "error", 110 | "text_height": "warning", 111 | "text_on_edge_cuts": "error", 112 | "text_thickness": "warning", 113 | "through_hole_pad_without_hole": "error", 114 | "too_many_vias": "error", 115 | "track_angle": "error", 116 | "track_dangling": "warning", 117 | "track_segment_length": "error", 118 | "track_width": "error", 119 | "tracks_crossing": "error", 120 | "unconnected_items": "error", 121 | "unresolved_variable": "error", 122 | "via_dangling": "warning", 123 | "zones_intersect": "error" 124 | }, 125 | "rules": { 126 | "max_error": 0.005, 127 | "min_clearance": 0.2, 128 | "min_connection": 0.0, 129 | "min_copper_edge_clearance": 0.3, 130 | "min_groove_width": 0.0, 131 | "min_hole_clearance": 0.25, 132 | "min_hole_to_hole": 0.25, 133 | "min_microvia_diameter": 0.2, 134 | "min_microvia_drill": 0.1, 135 | "min_resolved_spokes": 2, 136 | "min_silk_clearance": 0.0, 137 | "min_text_height": 0.8, 138 | "min_text_thickness": 0.08, 139 | "min_through_hole_diameter": 0.3, 140 | "min_track_width": 0.3, 141 | "min_via_annular_width": 0.1, 142 | "min_via_diameter": 0.4, 143 | "solder_mask_to_copper_clearance": 0.0, 144 | "use_height_for_length_calcs": true 145 | }, 146 | "teardrop_options": [ 147 | { 148 | "td_onpthpad": true, 149 | "td_onroundshapesonly": false, 150 | "td_onsmdpad": true, 151 | "td_ontrackend": false, 152 | "td_onvia": true 153 | } 154 | ], 155 | "teardrop_parameters": [ 156 | { 157 | "td_allow_use_two_tracks": true, 158 | "td_curve_segcount": 0, 159 | "td_height_ratio": 1.0, 160 | "td_length_ratio": 0.5, 161 | "td_maxheight": 2.0, 162 | "td_maxlen": 1.0, 163 | "td_on_pad_in_zone": false, 164 | "td_target_name": "td_round_shape", 165 | "td_width_to_size_filter_ratio": 0.9 166 | }, 167 | { 168 | "td_allow_use_two_tracks": true, 169 | "td_curve_segcount": 0, 170 | "td_height_ratio": 1.0, 171 | "td_length_ratio": 0.5, 172 | "td_maxheight": 2.0, 173 | "td_maxlen": 1.0, 174 | "td_on_pad_in_zone": false, 175 | "td_target_name": "td_rect_shape", 176 | "td_width_to_size_filter_ratio": 0.9 177 | }, 178 | { 179 | "td_allow_use_two_tracks": true, 180 | "td_curve_segcount": 0, 181 | "td_height_ratio": 1.0, 182 | "td_length_ratio": 0.5, 183 | "td_maxheight": 2.0, 184 | "td_maxlen": 1.0, 185 | "td_on_pad_in_zone": false, 186 | "td_target_name": "td_track_end", 187 | "td_width_to_size_filter_ratio": 0.9 188 | } 189 | ], 190 | "track_widths": [ 191 | 0.0 192 | ], 193 | "tuning_pattern_settings": { 194 | "diff_pair_defaults": { 195 | "corner_radius_percentage": 80, 196 | "corner_style": 1, 197 | "max_amplitude": 1.0, 198 | "min_amplitude": 0.2, 199 | "single_sided": false, 200 | "spacing": 1.0 201 | }, 202 | "diff_pair_skew_defaults": { 203 | "corner_radius_percentage": 80, 204 | "corner_style": 1, 205 | "max_amplitude": 1.0, 206 | "min_amplitude": 0.2, 207 | "single_sided": false, 208 | "spacing": 0.6 209 | }, 210 | "single_track_defaults": { 211 | "corner_radius_percentage": 80, 212 | "corner_style": 1, 213 | "max_amplitude": 1.0, 214 | "min_amplitude": 0.2, 215 | "single_sided": false, 216 | "spacing": 0.6 217 | } 218 | }, 219 | "via_dimensions": [ 220 | { 221 | "diameter": 0.0, 222 | "drill": 0.0 223 | } 224 | ], 225 | "zones_allow_external_fillets": false 226 | }, 227 | "ipc2581": { 228 | "dist": "", 229 | "distpn": "", 230 | "internal_id": "", 231 | "mfg": "", 232 | "mpn": "" 233 | }, 234 | "layer_pairs": [], 235 | "layer_presets": [], 236 | "viewports": [] 237 | }, 238 | "boards": [], 239 | "cvpcb": { 240 | "equivalence_files": [] 241 | }, 242 | "erc": { 243 | "erc_exclusions": [], 244 | "meta": { 245 | "version": 0 246 | }, 247 | "pin_map": [ 248 | [ 249 | 0, 250 | 0, 251 | 0, 252 | 0, 253 | 0, 254 | 0, 255 | 1, 256 | 0, 257 | 0, 258 | 0, 259 | 0, 260 | 2 261 | ], 262 | [ 263 | 0, 264 | 2, 265 | 0, 266 | 1, 267 | 0, 268 | 0, 269 | 1, 270 | 0, 271 | 2, 272 | 2, 273 | 2, 274 | 2 275 | ], 276 | [ 277 | 0, 278 | 0, 279 | 0, 280 | 0, 281 | 0, 282 | 0, 283 | 1, 284 | 0, 285 | 1, 286 | 0, 287 | 1, 288 | 2 289 | ], 290 | [ 291 | 0, 292 | 1, 293 | 0, 294 | 0, 295 | 0, 296 | 0, 297 | 1, 298 | 1, 299 | 2, 300 | 1, 301 | 1, 302 | 2 303 | ], 304 | [ 305 | 0, 306 | 0, 307 | 0, 308 | 0, 309 | 0, 310 | 0, 311 | 1, 312 | 0, 313 | 0, 314 | 0, 315 | 0, 316 | 2 317 | ], 318 | [ 319 | 0, 320 | 0, 321 | 0, 322 | 0, 323 | 0, 324 | 0, 325 | 0, 326 | 0, 327 | 0, 328 | 0, 329 | 0, 330 | 2 331 | ], 332 | [ 333 | 1, 334 | 1, 335 | 1, 336 | 1, 337 | 1, 338 | 0, 339 | 1, 340 | 1, 341 | 1, 342 | 1, 343 | 1, 344 | 2 345 | ], 346 | [ 347 | 0, 348 | 0, 349 | 0, 350 | 1, 351 | 0, 352 | 0, 353 | 1, 354 | 0, 355 | 0, 356 | 0, 357 | 0, 358 | 2 359 | ], 360 | [ 361 | 0, 362 | 2, 363 | 1, 364 | 2, 365 | 0, 366 | 0, 367 | 1, 368 | 0, 369 | 2, 370 | 2, 371 | 2, 372 | 2 373 | ], 374 | [ 375 | 0, 376 | 2, 377 | 0, 378 | 1, 379 | 0, 380 | 0, 381 | 1, 382 | 0, 383 | 2, 384 | 0, 385 | 0, 386 | 2 387 | ], 388 | [ 389 | 0, 390 | 2, 391 | 1, 392 | 1, 393 | 0, 394 | 0, 395 | 1, 396 | 0, 397 | 2, 398 | 0, 399 | 0, 400 | 2 401 | ], 402 | [ 403 | 2, 404 | 2, 405 | 2, 406 | 2, 407 | 2, 408 | 2, 409 | 2, 410 | 2, 411 | 2, 412 | 2, 413 | 2, 414 | 2 415 | ] 416 | ], 417 | "rule_severities": { 418 | "bus_definition_conflict": "error", 419 | "bus_entry_needed": "error", 420 | "bus_to_bus_conflict": "error", 421 | "bus_to_net_conflict": "error", 422 | "conflicting_netclasses": "error", 423 | "different_unit_footprint": "error", 424 | "different_unit_net": "error", 425 | "duplicate_reference": "error", 426 | "duplicate_sheet_names": "error", 427 | "endpoint_off_grid": "warning", 428 | "extra_units": "error", 429 | "footprint_filter": "ignore", 430 | "footprint_link_issues": "warning", 431 | "four_way_junction": "ignore", 432 | "global_label_dangling": "warning", 433 | "hier_label_mismatch": "error", 434 | "label_dangling": "error", 435 | "label_multiple_wires": "warning", 436 | "lib_symbol_issues": "warning", 437 | "lib_symbol_mismatch": "warning", 438 | "missing_bidi_pin": "warning", 439 | "missing_input_pin": "warning", 440 | "missing_power_pin": "error", 441 | "missing_unit": "warning", 442 | "multiple_net_names": "warning", 443 | "net_not_bus_member": "warning", 444 | "no_connect_connected": "warning", 445 | "no_connect_dangling": "warning", 446 | "pin_not_connected": "error", 447 | "pin_not_driven": "error", 448 | "pin_to_pin": "error", 449 | "power_pin_not_driven": "error", 450 | "same_local_global_label": "warning", 451 | "similar_label_and_power": "warning", 452 | "similar_labels": "warning", 453 | "similar_power": "warning", 454 | "simulation_model_issue": "ignore", 455 | "single_global_label": "ignore", 456 | "unannotated": "error", 457 | "unconnected_wire_endpoint": "warning", 458 | "unit_value_mismatch": "error", 459 | "unresolved_variable": "error", 460 | "wire_dangling": "error" 461 | } 462 | }, 463 | "libraries": { 464 | "pinned_footprint_libs": [], 465 | "pinned_symbol_libs": [] 466 | }, 467 | "meta": { 468 | "filename": "main.kicad_pro", 469 | "version": 3 470 | }, 471 | "net_settings": { 472 | "classes": [ 473 | { 474 | "bus_width": 12, 475 | "clearance": 0.2, 476 | "diff_pair_gap": 0.25, 477 | "diff_pair_via_gap": 0.25, 478 | "diff_pair_width": 0.2, 479 | "line_style": 0, 480 | "microvia_diameter": 0.3, 481 | "microvia_drill": 0.1, 482 | "name": "Default", 483 | "pcb_color": "rgba(0, 0, 0, 0.000)", 484 | "priority": 2147483647, 485 | "schematic_color": "rgba(0, 0, 0, 0.000)", 486 | "track_width": 0.2, 487 | "via_diameter": 0.6, 488 | "via_drill": 0.3, 489 | "wire_width": 6 490 | } 491 | ], 492 | "meta": { 493 | "version": 4 494 | }, 495 | "net_colors": null, 496 | "netclass_assignments": null, 497 | "netclass_patterns": [] 498 | }, 499 | "pcbnew": { 500 | "last_paths": { 501 | "gencad": "", 502 | "idf": "", 503 | "netlist": "", 504 | "plot": "output/", 505 | "pos_files": "", 506 | "specctra_dsn": "", 507 | "step": "", 508 | "svg": "", 509 | "vrml": "" 510 | }, 511 | "page_layout_descr_file": "" 512 | }, 513 | "schematic": { 514 | "annotate_start_num": 0, 515 | "bom_export_filename": "", 516 | "bom_fmt_presets": [], 517 | "bom_fmt_settings": { 518 | "field_delimiter": ",", 519 | "keep_line_breaks": false, 520 | "keep_tabs": false, 521 | "name": "CSV", 522 | "ref_delimiter": ",", 523 | "ref_range_delimiter": "", 524 | "string_delimiter": "\"" 525 | }, 526 | "bom_presets": [], 527 | "bom_settings": { 528 | "exclude_dnp": false, 529 | "fields_ordered": [ 530 | { 531 | "group_by": false, 532 | "label": "Reference", 533 | "name": "Reference", 534 | "show": true 535 | }, 536 | { 537 | "group_by": true, 538 | "label": "Value", 539 | "name": "Value", 540 | "show": true 541 | }, 542 | { 543 | "group_by": false, 544 | "label": "Datasheet", 545 | "name": "Datasheet", 546 | "show": true 547 | }, 548 | { 549 | "group_by": false, 550 | "label": "Footprint", 551 | "name": "Footprint", 552 | "show": true 553 | }, 554 | { 555 | "group_by": false, 556 | "label": "Qty", 557 | "name": "${QUANTITY}", 558 | "show": true 559 | }, 560 | { 561 | "group_by": true, 562 | "label": "DNP", 563 | "name": "${DNP}", 564 | "show": true 565 | }, 566 | { 567 | "group_by": false, 568 | "label": "#", 569 | "name": "${ITEM_NUMBER}", 570 | "show": false 571 | }, 572 | { 573 | "group_by": false, 574 | "label": "Description", 575 | "name": "Description", 576 | "show": false 577 | } 578 | ], 579 | "filter_string": "", 580 | "group_symbols": true, 581 | "include_excluded_from_bom": false, 582 | "name": "", 583 | "sort_asc": true, 584 | "sort_field": "Reference" 585 | }, 586 | "connection_grid_size": 50.0, 587 | "drawing": { 588 | "dashed_lines_dash_length_ratio": 12.0, 589 | "dashed_lines_gap_length_ratio": 3.0, 590 | "default_line_thickness": 6.0, 591 | "default_text_size": 50.0, 592 | "field_names": [], 593 | "intersheets_ref_own_page": false, 594 | "intersheets_ref_prefix": "", 595 | "intersheets_ref_short": false, 596 | "intersheets_ref_show": false, 597 | "intersheets_ref_suffix": "", 598 | "junction_size_choice": 3, 599 | "label_size_ratio": 0.375, 600 | "operating_point_overlay_i_precision": 3, 601 | "operating_point_overlay_i_range": "~A", 602 | "operating_point_overlay_v_precision": 3, 603 | "operating_point_overlay_v_range": "~V", 604 | "overbar_offset_ratio": 1.23, 605 | "pin_symbol_size": 25.0, 606 | "text_offset_ratio": 0.15 607 | }, 608 | "legacy_lib_dir": "", 609 | "legacy_lib_list": [], 610 | "meta": { 611 | "version": 1 612 | }, 613 | "net_format_name": "", 614 | "page_layout_descr_file": "", 615 | "plot_directory": "", 616 | "space_save_all_events": true, 617 | "spice_current_sheet_as_root": false, 618 | "spice_external_command": "spice \"%I\"", 619 | "spice_model_current_sheet_as_root": true, 620 | "spice_save_all_currents": false, 621 | "spice_save_all_dissipations": false, 622 | "spice_save_all_voltages": false, 623 | "subpart_first_id": 65, 624 | "subpart_id_separator": 0 625 | }, 626 | "sheets": [ 627 | [ 628 | "8c2a105f-a962-4917-8f56-dd50da121d32", 629 | "Root" 630 | ] 631 | ], 632 | "text_variables": {} 633 | } 634 | -------------------------------------------------------------------------------- /hardware/board/main.kicad_sym: -------------------------------------------------------------------------------- 1 | (kicad_symbol_lib 2 | (version 20231120) 3 | (generator "kicad_symbol_editor") 4 | (generator_version "8.0") 5 | (symbol "OLED" 6 | (exclude_from_sim no) 7 | (in_bom yes) 8 | (on_board yes) 9 | (property "Reference" "U" 10 | (at 0 0 0) 11 | (effects 12 | (font 13 | (size 1.27 1.27) 14 | ) 15 | ) 16 | ) 17 | (property "Value" "" 18 | (at 0 5.08 0) 19 | (effects 20 | (font 21 | (size 1.27 1.27) 22 | ) 23 | ) 24 | ) 25 | (property "Footprint" "" 26 | (at 0 5.08 0) 27 | (effects 28 | (font 29 | (size 1.27 1.27) 30 | ) 31 | (hide yes) 32 | ) 33 | ) 34 | (property "Datasheet" "" 35 | (at 0 5.08 0) 36 | (effects 37 | (font 38 | (size 1.27 1.27) 39 | ) 40 | (hide yes) 41 | ) 42 | ) 43 | (property "Description" "" 44 | (at 0 5.08 0) 45 | (effects 46 | (font 47 | (size 1.27 1.27) 48 | ) 49 | (hide yes) 50 | ) 51 | ) 52 | (symbol "OLED_1_1" 53 | (rectangle 54 | (start -8.89 21.59) 55 | (end 8.89 -21.59) 56 | (stroke 57 | (width 0) 58 | (type default) 59 | ) 60 | (fill 61 | (type background) 62 | ) 63 | ) 64 | (pin passive line 65 | (at -11.43 15.24 0) 66 | (length 2.54) 67 | (name "C2P" 68 | (effects 69 | (font 70 | (size 1.27 1.27) 71 | ) 72 | ) 73 | ) 74 | (number "1" 75 | (effects 76 | (font 77 | (size 1.27 1.27) 78 | ) 79 | ) 80 | ) 81 | ) 82 | (pin input line 83 | (at 11.43 -10.16 180) 84 | (length 2.54) 85 | (name "DC" 86 | (effects 87 | (font 88 | (size 1.27 1.27) 89 | ) 90 | ) 91 | ) 92 | (number "10" 93 | (effects 94 | (font 95 | (size 1.27 1.27) 96 | ) 97 | ) 98 | ) 99 | ) 100 | (pin input line 101 | (at 11.43 -12.7 180) 102 | (length 2.54) 103 | (name "SCLK" 104 | (effects 105 | (font 106 | (size 1.27 1.27) 107 | ) 108 | ) 109 | ) 110 | (number "11" 111 | (effects 112 | (font 113 | (size 1.27 1.27) 114 | ) 115 | ) 116 | ) 117 | ) 118 | (pin input line 119 | (at 11.43 -15.24 180) 120 | (length 2.54) 121 | (name "SDIN" 122 | (effects 123 | (font 124 | (size 1.27 1.27) 125 | ) 126 | ) 127 | ) 128 | (number "12" 129 | (effects 130 | (font 131 | (size 1.27 1.27) 132 | ) 133 | ) 134 | ) 135 | ) 136 | (pin passive line 137 | (at -11.43 -2.54 0) 138 | (length 2.54) 139 | (name "IREF" 140 | (effects 141 | (font 142 | (size 1.27 1.27) 143 | ) 144 | ) 145 | ) 146 | (number "13" 147 | (effects 148 | (font 149 | (size 1.27 1.27) 150 | ) 151 | ) 152 | ) 153 | ) 154 | (pin passive line 155 | (at -11.43 -12.7 0) 156 | (length 2.54) 157 | (name "VCOMH" 158 | (effects 159 | (font 160 | (size 1.27 1.27) 161 | ) 162 | ) 163 | ) 164 | (number "14" 165 | (effects 166 | (font 167 | (size 1.27 1.27) 168 | ) 169 | ) 170 | ) 171 | ) 172 | (pin power_in line 173 | (at -11.43 -15.24 0) 174 | (length 2.54) 175 | (name "VCC" 176 | (effects 177 | (font 178 | (size 1.27 1.27) 179 | ) 180 | ) 181 | ) 182 | (number "15" 183 | (effects 184 | (font 185 | (size 1.27 1.27) 186 | ) 187 | ) 188 | ) 189 | ) 190 | (pin passive line 191 | (at -11.43 12.7 0) 192 | (length 2.54) 193 | (name "C2N" 194 | (effects 195 | (font 196 | (size 1.27 1.27) 197 | ) 198 | ) 199 | ) 200 | (number "2" 201 | (effects 202 | (font 203 | (size 1.27 1.27) 204 | ) 205 | ) 206 | ) 207 | ) 208 | (pin passive line 209 | (at -11.43 10.16 0) 210 | (length 2.54) 211 | (name "C1P" 212 | (effects 213 | (font 214 | (size 1.27 1.27) 215 | ) 216 | ) 217 | ) 218 | (number "3" 219 | (effects 220 | (font 221 | (size 1.27 1.27) 222 | ) 223 | ) 224 | ) 225 | ) 226 | (pin passive line 227 | (at -11.43 7.62 0) 228 | (length 2.54) 229 | (name "C1N" 230 | (effects 231 | (font 232 | (size 1.27 1.27) 233 | ) 234 | ) 235 | ) 236 | (number "4" 237 | (effects 238 | (font 239 | (size 1.27 1.27) 240 | ) 241 | ) 242 | ) 243 | ) 244 | (pin power_in line 245 | (at -2.54 24.13 270) 246 | (length 2.54) 247 | (name "VBAT" 248 | (effects 249 | (font 250 | (size 1.27 1.27) 251 | ) 252 | ) 253 | ) 254 | (number "5" 255 | (effects 256 | (font 257 | (size 1.27 1.27) 258 | ) 259 | ) 260 | ) 261 | ) 262 | (pin power_in line 263 | (at 0 -24.13 90) 264 | (length 2.54) 265 | (name "VSS" 266 | (effects 267 | (font 268 | (size 1.27 1.27) 269 | ) 270 | ) 271 | ) 272 | (number "6" 273 | (effects 274 | (font 275 | (size 1.27 1.27) 276 | ) 277 | ) 278 | ) 279 | ) 280 | (pin power_in line 281 | (at 2.54 24.13 270) 282 | (length 2.54) 283 | (name "VDD" 284 | (effects 285 | (font 286 | (size 1.27 1.27) 287 | ) 288 | ) 289 | ) 290 | (number "7" 291 | (effects 292 | (font 293 | (size 1.27 1.27) 294 | ) 295 | ) 296 | ) 297 | ) 298 | (pin input line 299 | (at 11.43 -5.08 180) 300 | (length 2.54) 301 | (name "CS" 302 | (effects 303 | (font 304 | (size 1.27 1.27) 305 | ) 306 | ) 307 | ) 308 | (number "8" 309 | (effects 310 | (font 311 | (size 1.27 1.27) 312 | ) 313 | ) 314 | ) 315 | ) 316 | (pin input line 317 | (at 11.43 -7.62 180) 318 | (length 2.54) 319 | (name "RESET" 320 | (effects 321 | (font 322 | (size 1.27 1.27) 323 | ) 324 | ) 325 | ) 326 | (number "9" 327 | (effects 328 | (font 329 | (size 1.27 1.27) 330 | ) 331 | ) 332 | ) 333 | ) 334 | ) 335 | ) 336 | (symbol "SW_SPDT_MP" 337 | (pin_names 338 | (offset 0) hide) 339 | (exclude_from_sim no) 340 | (in_bom yes) 341 | (on_board yes) 342 | (property "Reference" "SW" 343 | (at 0 7.62 0) 344 | (effects 345 | (font 346 | (size 1.27 1.27) 347 | ) 348 | ) 349 | ) 350 | (property "Value" "SW_SPDT_MP" 351 | (at 0 5.588 0) 352 | (effects 353 | (font 354 | (size 1.27 1.27) 355 | ) 356 | ) 357 | ) 358 | (property "Footprint" "" 359 | (at 0 0 0) 360 | (effects 361 | (font 362 | (size 1.27 1.27) 363 | ) 364 | (hide yes) 365 | ) 366 | ) 367 | (property "Datasheet" "~" 368 | (at 0 7.62 0) 369 | (effects 370 | (font 371 | (size 1.27 1.27) 372 | ) 373 | (hide yes) 374 | ) 375 | ) 376 | (property "Description" "Switch, single pole double throw" 377 | (at 0 0 0) 378 | (effects 379 | (font 380 | (size 1.27 1.27) 381 | ) 382 | (hide yes) 383 | ) 384 | ) 385 | (property "ki_keywords" "switch single-pole double-throw spdt ON-ON" 386 | (at 0 0 0) 387 | (effects 388 | (font 389 | (size 1.27 1.27) 390 | ) 391 | (hide yes) 392 | ) 393 | ) 394 | (symbol "SW_SPDT_MP_0_1" 395 | (circle 396 | (center -2.032 0) 397 | (radius 0.4572) 398 | (stroke 399 | (width 0) 400 | (type default) 401 | ) 402 | (fill 403 | (type none) 404 | ) 405 | ) 406 | (polyline 407 | (pts 408 | (xy -1.6156 -0.2125) (xy 1.6864 -2.2445) 409 | ) 410 | (stroke 411 | (width 0) 412 | (type default) 413 | ) 414 | (fill 415 | (type none) 416 | ) 417 | ) 418 | (circle 419 | (center 2.032 -2.54) 420 | (radius 0.4572) 421 | (stroke 422 | (width 0) 423 | (type default) 424 | ) 425 | (fill 426 | (type none) 427 | ) 428 | ) 429 | (circle 430 | (center 2.032 2.54) 431 | (radius 0.4572) 432 | (stroke 433 | (width 0) 434 | (type default) 435 | ) 436 | (fill 437 | (type none) 438 | ) 439 | ) 440 | ) 441 | (symbol "SW_SPDT_MP_1_1" 442 | (rectangle 443 | (start -3.175 3.81) 444 | (end 3.175 -3.81) 445 | (stroke 446 | (width 0) 447 | (type default) 448 | ) 449 | (fill 450 | (type background) 451 | ) 452 | ) 453 | (pin passive line 454 | (at 5.08 -2.54 180) 455 | (length 2.54) 456 | (name "A" 457 | (effects 458 | (font 459 | (size 1.27 1.27) 460 | ) 461 | ) 462 | ) 463 | (number "1" 464 | (effects 465 | (font 466 | (size 1.27 1.27) 467 | ) 468 | ) 469 | ) 470 | ) 471 | (pin passive line 472 | (at -5.08 0 0) 473 | (length 2.54) 474 | (name "B" 475 | (effects 476 | (font 477 | (size 1.27 1.27) 478 | ) 479 | ) 480 | ) 481 | (number "2" 482 | (effects 483 | (font 484 | (size 1.27 1.27) 485 | ) 486 | ) 487 | ) 488 | ) 489 | (pin passive line 490 | (at 5.08 2.54 180) 491 | (length 2.54) 492 | (name "C" 493 | (effects 494 | (font 495 | (size 1.27 1.27) 496 | ) 497 | ) 498 | ) 499 | (number "3" 500 | (effects 501 | (font 502 | (size 1.27 1.27) 503 | ) 504 | ) 505 | ) 506 | ) 507 | (pin passive line 508 | (at 0 -6.35 90) 509 | (length 2.54) 510 | (name "" 511 | (effects 512 | (font 513 | (size 1.27 1.27) 514 | ) 515 | ) 516 | ) 517 | (number "G" 518 | (effects 519 | (font 520 | (size 1.27 1.27) 521 | ) 522 | ) 523 | ) 524 | ) 525 | ) 526 | ) 527 | ) 528 | -------------------------------------------------------------------------------- /hardware/board/main.pretty/BatteryHolder_MPD_BC2003_1x2032.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "BatteryHolder_MPD_BC2003_1x2032" 2 | (version 20241229) 3 | (generator "pcbnew") 4 | (generator_version "9.0") 5 | (layer "F.Cu") 6 | (descr "http://www.memoryprotectiondevices.com/datasheets/BC-2003-datasheet.pdf") 7 | (tags "BC2003 CR2032 2032 Battery Holder") 8 | (property "Reference" "BT1" 9 | (at 0 -11.2 0) 10 | (layer "F.SilkS") 11 | (hide yes) 12 | (uuid "58403f4a-c81b-4e64-af83-3060666a9269") 13 | (effects 14 | (font 15 | (size 1 1) 16 | (thickness 0.15) 17 | ) 18 | ) 19 | ) 20 | (property "Value" "~" 21 | (at 0 11.2 0) 22 | (layer "F.Fab") 23 | (uuid "ce9f6bfb-26a7-46f0-869b-ca302552d1d9") 24 | (effects 25 | (font 26 | (size 1 1) 27 | (thickness 0.15) 28 | ) 29 | ) 30 | ) 31 | (property "Datasheet" "" 32 | (at 0 0 0) 33 | (unlocked yes) 34 | (layer "F.Fab") 35 | (hide yes) 36 | (uuid "b0251600-6570-4f36-bc13-5de4731afd7b") 37 | (effects 38 | (font 39 | (size 1.27 1.27) 40 | (thickness 0.15) 41 | ) 42 | ) 43 | ) 44 | (property "Description" "Single-cell battery" 45 | (at 0 0 0) 46 | (unlocked yes) 47 | (layer "F.Fab") 48 | (hide yes) 49 | (uuid "1dd2611b-b857-49b0-aae9-c7bf3f2c16a0") 50 | (effects 51 | (font 52 | (size 1.27 1.27) 53 | (thickness 0.15) 54 | ) 55 | ) 56 | ) 57 | (attr smd) 58 | (fp_line 59 | (start -10.47 -4.885001) 60 | (end -8.86291 -4.885) 61 | (stroke 62 | (width 0.12) 63 | (type solid) 64 | ) 65 | (layer "F.SilkS") 66 | (uuid "eefdbdbc-e570-463b-9781-8076ef889d8b") 67 | ) 68 | (fp_line 69 | (start -10.47 -3) 70 | (end -10.47 -4.885001) 71 | (stroke 72 | (width 0.12) 73 | (type solid) 74 | ) 75 | (layer "F.SilkS") 76 | (uuid "edb7fadb-8fcb-4b4a-ab37-67d2df4310ed") 77 | ) 78 | (fp_line 79 | (start 10.47 -4.885001) 80 | (end 8.86291 -4.885) 81 | (stroke 82 | (width 0.12) 83 | (type solid) 84 | ) 85 | (layer "F.SilkS") 86 | (uuid "1c92f8fc-b862-4e32-9f81-230dd252bcea") 87 | ) 88 | (fp_line 89 | (start 10.47 -3) 90 | (end 10.47 -4.885001) 91 | (stroke 92 | (width 0.12) 93 | (type solid) 94 | ) 95 | (layer "F.SilkS") 96 | (uuid "97b63cf1-6420-47bc-8a28-abb9b253528f") 97 | ) 98 | (fp_line 99 | (start 10.47 3) 100 | (end 10.47 4.885001) 101 | (stroke 102 | (width 0.12) 103 | (type solid) 104 | ) 105 | (layer "F.SilkS") 106 | (uuid "51fc4e12-8dbe-4361-b0ff-3b88fa050d9a") 107 | ) 108 | (fp_line 109 | (start 10.47 4.885001) 110 | (end 8.86291 4.885) 111 | (stroke 112 | (width 0.12) 113 | (type solid) 114 | ) 115 | (layer "F.SilkS") 116 | (uuid "97306a4b-c362-4058-8bd3-ccd8e1e13702") 117 | ) 118 | (fp_arc 119 | (start -6.350001 -8.255) 120 | (mid -4.802768 -9.241263) 121 | (end -3.106466 -9.940693) 122 | (stroke 123 | (width 0.12) 124 | (type solid) 125 | ) 126 | (layer "F.SilkS") 127 | (uuid "484268e3-64d5-4040-9c01-fe47d832942e") 128 | ) 129 | (fp_arc 130 | (start 3.106466 -9.940693) 131 | (mid 4.802768 -9.241262) 132 | (end 6.350001 -8.255) 133 | (stroke 134 | (width 0.12) 135 | (type solid) 136 | ) 137 | (layer "F.SilkS") 138 | (uuid "d9c8307c-37d6-4c3d-930f-95aad9027d71") 139 | ) 140 | (fp_line 141 | (start -13.45 -3.03) 142 | (end -13.45 3.03) 143 | (stroke 144 | (width 0.05) 145 | (type solid) 146 | ) 147 | (layer "F.CrtYd") 148 | (uuid "7b562574-36a9-4929-8c1c-999e004ff1fc") 149 | ) 150 | (fp_line 151 | (start -13.45 3.03) 152 | (end -10.599999 3.03) 153 | (stroke 154 | (width 0.05) 155 | (type solid) 156 | ) 157 | (layer "F.CrtYd") 158 | (uuid "25bd1ff2-7103-4b0f-a49e-8adceab552ef") 159 | ) 160 | (fp_line 161 | (start -10.6 -5.01) 162 | (end -10.599999 -3.03) 163 | (stroke 164 | (width 0.05) 165 | (type solid) 166 | ) 167 | (layer "F.CrtYd") 168 | (uuid "6ed25056-b0b2-4c3c-b152-674d53b3bb65") 169 | ) 170 | (fp_line 171 | (start -10.6 5.01) 172 | (end -10.599999 3.03) 173 | (stroke 174 | (width 0.05) 175 | (type solid) 176 | ) 177 | (layer "F.CrtYd") 178 | (uuid "5e4fe63e-6350-468f-8cdd-b0e8340c58e8") 179 | ) 180 | (fp_line 181 | (start -10.6 5.01) 182 | (end -8.940001 5.01) 183 | (stroke 184 | (width 0.05) 185 | (type solid) 186 | ) 187 | (layer "F.CrtYd") 188 | (uuid "ff04b32c-1205-41bc-a7df-2563c963dae0") 189 | ) 190 | (fp_line 191 | (start -10.599999 -3.03) 192 | (end -13.45 -3.03) 193 | (stroke 194 | (width 0.05) 195 | (type solid) 196 | ) 197 | (layer "F.CrtYd") 198 | (uuid "39b5c624-7b72-4ed1-9cea-69dd9dbea58c") 199 | ) 200 | (fp_line 201 | (start -8.940001 -5.01) 202 | (end -10.6 -5.01) 203 | (stroke 204 | (width 0.05) 205 | (type solid) 206 | ) 207 | (layer "F.CrtYd") 208 | (uuid "6801d48a-f6af-488f-a408-f8c9c69a3ffe") 209 | ) 210 | (fp_line 211 | (start 8.940001 -5.01) 212 | (end 10.6 -5.01) 213 | (stroke 214 | (width 0.05) 215 | (type solid) 216 | ) 217 | (layer "F.CrtYd") 218 | (uuid "cdc66922-f011-48ba-a716-0f81327d3c4b") 219 | ) 220 | (fp_line 221 | (start 8.940001 5.01) 222 | (end 10.6 5.01) 223 | (stroke 224 | (width 0.05) 225 | (type solid) 226 | ) 227 | (layer "F.CrtYd") 228 | (uuid "a1a647bd-c4dc-461a-9101-f60ef5a10367") 229 | ) 230 | (fp_line 231 | (start 10.599999 -3.03) 232 | (end 13.45 -3.03) 233 | (stroke 234 | (width 0.05) 235 | (type solid) 236 | ) 237 | (layer "F.CrtYd") 238 | (uuid "7827d2d8-faf3-4858-9009-bd981b10db2f") 239 | ) 240 | (fp_line 241 | (start 10.599999 3.03) 242 | (end 13.45 3.03) 243 | (stroke 244 | (width 0.05) 245 | (type solid) 246 | ) 247 | (layer "F.CrtYd") 248 | (uuid "7c56b0aa-e81c-4216-9ebc-9dfe6fa8ced2") 249 | ) 250 | (fp_line 251 | (start 10.6 -5.01) 252 | (end 10.599999 -3.03) 253 | (stroke 254 | (width 0.05) 255 | (type solid) 256 | ) 257 | (layer "F.CrtYd") 258 | (uuid "8e80377f-9397-4eb9-b311-79ca64759c96") 259 | ) 260 | (fp_line 261 | (start 10.6 5.01) 262 | (end 10.599999 3.03) 263 | (stroke 264 | (width 0.05) 265 | (type solid) 266 | ) 267 | (layer "F.CrtYd") 268 | (uuid "d2c1e56d-1c33-44c1-89cc-f42771467ef1") 269 | ) 270 | (fp_line 271 | (start 13.45 -3.03) 272 | (end 13.45 3.03) 273 | (stroke 274 | (width 0.05) 275 | (type solid) 276 | ) 277 | (layer "F.CrtYd") 278 | (uuid "dd0e4098-d179-40fc-b6f0-dd1cba50509d") 279 | ) 280 | (fp_arc 281 | (start -8.934116 -5.020486) 282 | (mid 0.006012 -10.248106) 283 | (end 8.940001 -5.01) 284 | (stroke 285 | (width 0.05) 286 | (type solid) 287 | ) 288 | (layer "F.CrtYd") 289 | (uuid "af45deaf-1939-413c-abd8-4f50886a5993") 290 | ) 291 | (fp_arc 292 | (start 8.925339 5.036071) 293 | (mid -0.014956 10.248096) 294 | (end -8.940001 5.01) 295 | (stroke 296 | (width 0.05) 297 | (type solid) 298 | ) 299 | (layer "F.CrtYd") 300 | (uuid "22ba3bc8-29e0-42a3-8b67-628de86a04ef") 301 | ) 302 | (fp_line 303 | (start -12.7 -1.825) 304 | (end -12 -2.525) 305 | (stroke 306 | (width 0.1) 307 | (type solid) 308 | ) 309 | (layer "F.Fab") 310 | (uuid "0a8c0eef-66d3-405d-8c6c-7540203fae64") 311 | ) 312 | (fp_line 313 | (start -12.7 1.825) 314 | (end -12 2.525) 315 | (stroke 316 | (width 0.1) 317 | (type solid) 318 | ) 319 | (layer "F.Fab") 320 | (uuid "8acf4b17-7d75-47e5-855d-5ea09f3eb8e6") 321 | ) 322 | (fp_line 323 | (start -10.35 -4.765) 324 | (end 10.35 -4.765) 325 | (stroke 326 | (width 0.1) 327 | (type solid) 328 | ) 329 | (layer "F.Fab") 330 | (uuid "f021caff-4bb0-498d-82b2-ac47a093d49f") 331 | ) 332 | (fp_line 333 | (start -10.35 -2.525) 334 | (end -12 -2.525) 335 | (stroke 336 | (width 0.1) 337 | (type solid) 338 | ) 339 | (layer "F.Fab") 340 | (uuid "7ac407e1-99c8-462c-955f-900e5cccf610") 341 | ) 342 | (fp_line 343 | (start -10.35 2.525) 344 | (end -12 2.525) 345 | (stroke 346 | (width 0.1) 347 | (type solid) 348 | ) 349 | (layer "F.Fab") 350 | (uuid "5f93f0e1-fd9c-49a0-9e29-b0ad275e0a8e") 351 | ) 352 | (fp_line 353 | (start -10.35 4.765) 354 | (end -10.35 -4.765) 355 | (stroke 356 | (width 0.1) 357 | (type solid) 358 | ) 359 | (layer "F.Fab") 360 | (uuid "ecedc00d-bcf8-4d3d-a8dd-fcb194dfe45c") 361 | ) 362 | (fp_line 363 | (start -10.35 4.765) 364 | (end 10.35 4.765) 365 | (stroke 366 | (width 0.1) 367 | (type solid) 368 | ) 369 | (layer "F.Fab") 370 | (uuid "32a48109-644a-441c-a788-06cc50b8abb0") 371 | ) 372 | (fp_line 373 | (start 10.35 -2.525) 374 | (end 12 -2.525) 375 | (stroke 376 | (width 0.1) 377 | (type solid) 378 | ) 379 | (layer "F.Fab") 380 | (uuid "80bc4a89-042e-459d-8e10-e2e171ff91cf") 381 | ) 382 | (fp_line 383 | (start 10.35 2.525) 384 | (end 12 2.525) 385 | (stroke 386 | (width 0.1) 387 | (type solid) 388 | ) 389 | (layer "F.Fab") 390 | (uuid "917f93b4-f00e-41cc-8f61-0e4ecde7dc9c") 391 | ) 392 | (fp_line 393 | (start 10.35 4.765) 394 | (end 10.35 -4.765) 395 | (stroke 396 | (width 0.1) 397 | (type solid) 398 | ) 399 | (layer "F.Fab") 400 | (uuid "38be7f14-3124-463d-8df2-326d867fccfb") 401 | ) 402 | (fp_line 403 | (start 12.7 -1.825) 404 | (end 12 -2.525) 405 | (stroke 406 | (width 0.1) 407 | (type solid) 408 | ) 409 | (layer "F.Fab") 410 | (uuid "324f2b59-f42c-4f7f-9e44-b4a19f9798ba") 411 | ) 412 | (fp_line 413 | (start 12.7 -1.825) 414 | (end 12.7 1.825) 415 | (stroke 416 | (width 0.1) 417 | (type solid) 418 | ) 419 | (layer "F.Fab") 420 | (uuid "682c80da-6c70-43c5-bcb8-05d737080091") 421 | ) 422 | (fp_line 423 | (start 12.7 1.825) 424 | (end 12 2.525) 425 | (stroke 426 | (width 0.1) 427 | (type solid) 428 | ) 429 | (layer "F.Fab") 430 | (uuid "954fd33e-ee01-430f-be09-15d7d4d22e45") 431 | ) 432 | (fp_text user "${REFERENCE}" 433 | (at 0 0 0) 434 | (layer "F.Fab") 435 | (uuid "6900c53c-87fa-4570-829a-f28a9ee2eedd") 436 | (effects 437 | (font 438 | (size 1 1) 439 | (thickness 0.15) 440 | ) 441 | ) 442 | ) 443 | (pad "1" smd rect 444 | (at -11.905 0) 445 | (size 2.6 5.56) 446 | (layers "F.Cu" "F.Mask") 447 | (uuid "6abd491b-329d-4e55-9880-3a85bf1fe8cc") 448 | ) 449 | (pad "1" smd rect 450 | (at 11.905 0) 451 | (size 2.6 5.56) 452 | (layers "F.Cu" "F.Mask") 453 | (uuid "7c5e7aaa-ca6a-46ae-9078-2605659de699") 454 | ) 455 | (pad "2" smd circle 456 | (at 0 0) 457 | (size 17.8 17.8) 458 | (layers "F.Cu" "F.Mask") 459 | (uuid "d4708b21-92ef-4862-8329-8736b5531b25") 460 | ) 461 | (embedded_fonts no) 462 | (model "${KICAD9_3DMODEL_DIR}/Battery.3dshapes/BatteryHolder_MPD_BC2003_1x2032.step" 463 | (offset 464 | (xyz 0 0 0) 465 | ) 466 | (scale 467 | (xyz 1 1 1) 468 | ) 469 | (rotate 470 | (xyz 0 0 0) 471 | ) 472 | ) 473 | ) 474 | -------------------------------------------------------------------------------- /hardware/board/main.pretty/MountingHole_2.2mm_M2_Pad_Small.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "MountingHole_2.2mm_M2_Pad_Small" 2 | (version 20241229) 3 | (generator "pcbnew") 4 | (generator_version "9.0") 5 | (layer "F.Cu") 6 | (descr "Mounting Hole 2.2mm, M2") 7 | (tags "mounting hole 2.2mm m2") 8 | (property "Reference" "REF**" 9 | (at 0 -3.2 0) 10 | (layer "F.SilkS") 11 | (hide yes) 12 | (uuid "63e9c37d-9f9d-458f-b38a-eec4c7a6b3e6") 13 | (effects 14 | (font 15 | (size 1 1) 16 | (thickness 0.15) 17 | ) 18 | ) 19 | ) 20 | (property "Value" "~" 21 | (at 0 3.2 0) 22 | (layer "F.Fab") 23 | (uuid "fdcd9499-3d1c-487d-9d9e-fc8b990022a9") 24 | (effects 25 | (font 26 | (size 1 1) 27 | (thickness 0.15) 28 | ) 29 | ) 30 | ) 31 | (property "Datasheet" "" 32 | (at 0 0 0) 33 | (unlocked yes) 34 | (layer "F.Fab") 35 | (hide yes) 36 | (uuid "7efe5017-4e4f-4582-a68b-0bf1aeb60f53") 37 | (effects 38 | (font 39 | (size 1.27 1.27) 40 | (thickness 0.15) 41 | ) 42 | ) 43 | ) 44 | (property "Description" "Mounting Hole with connection" 45 | (at 0 0 0) 46 | (unlocked yes) 47 | (layer "F.Fab") 48 | (hide yes) 49 | (uuid "3c6eddbb-c55e-4844-b8df-17bcdddcbe8b") 50 | (effects 51 | (font 52 | (size 1.27 1.27) 53 | (thickness 0.15) 54 | ) 55 | ) 56 | ) 57 | (attr exclude_from_pos_files exclude_from_bom) 58 | (fp_circle 59 | (center 0 0) 60 | (end 2.2 0) 61 | (stroke 62 | (width 0.15) 63 | (type solid) 64 | ) 65 | (fill no) 66 | (layer "Cmts.User") 67 | (uuid "917c0846-ddb5-4bc3-af59-17ad4f380fab") 68 | ) 69 | (fp_circle 70 | (center 0 0) 71 | (end 2.45 0) 72 | (stroke 73 | (width 0.05) 74 | (type solid) 75 | ) 76 | (fill no) 77 | (layer "F.CrtYd") 78 | (uuid "7666ec59-b781-4c26-8f93-2138e474745d") 79 | ) 80 | (fp_text user "${REFERENCE}" 81 | (at 0 0 0) 82 | (layer "F.Fab") 83 | (uuid "bfe33e41-7021-4a23-a92d-6bf125feb2f0") 84 | (effects 85 | (font 86 | (size 1 1) 87 | (thickness 0.15) 88 | ) 89 | ) 90 | ) 91 | (pad "1" thru_hole circle 92 | (at 0 0) 93 | (size 2.8 2.8) 94 | (drill 2.2) 95 | (layers "*.Cu" "*.Mask") 96 | (remove_unused_layers no) 97 | (uuid "6afb5c57-77c8-4a3b-ab54-59cf9ba37d76") 98 | ) 99 | (embedded_fonts no) 100 | ) 101 | -------------------------------------------------------------------------------- /hardware/board/main.pretty/REX012832.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "REX012832" 2 | (version 20241229) 3 | (generator "pcbnew") 4 | (generator_version "9.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -0.5 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (uuid "4791fe9d-e62f-4dee-82f2-219869a073f2") 11 | (effects 12 | (font 13 | (size 1 1) 14 | (thickness 0.15) 15 | ) 16 | ) 17 | ) 18 | (property "Value" "~" 19 | (at 0 1 0) 20 | (unlocked yes) 21 | (layer "F.Fab") 22 | (uuid "07a8ef4d-62c8-46c4-90bd-20639b85727f") 23 | (effects 24 | (font 25 | (size 1 1) 26 | (thickness 0.15) 27 | ) 28 | ) 29 | ) 30 | (property "Datasheet" "" 31 | (at 0 0 0) 32 | (unlocked yes) 33 | (layer "F.Fab") 34 | (hide yes) 35 | (uuid "88dc305b-4f32-4720-9b37-2a36d9f2291e") 36 | (effects 37 | (font 38 | (size 1 1) 39 | (thickness 0.15) 40 | ) 41 | ) 42 | ) 43 | (property "Description" "" 44 | (at 0 0 0) 45 | (unlocked yes) 46 | (layer "F.Fab") 47 | (hide yes) 48 | (uuid "0c99f38f-867a-4584-888c-5dde2e4e9228") 49 | (effects 50 | (font 51 | (size 1 1) 52 | (thickness 0.15) 53 | ) 54 | ) 55 | ) 56 | (attr smd) 57 | (fp_line 58 | (start -13.3 -6) 59 | (end -12.3 -6) 60 | (stroke 61 | (width 0.1) 62 | (type default) 63 | ) 64 | (layer "F.SilkS") 65 | (uuid "1e351a8f-0483-4816-9951-947cbdd9a63d") 66 | ) 67 | (fp_line 68 | (start -13.3 -5) 69 | (end -13.3 -6) 70 | (stroke 71 | (width 0.1) 72 | (type default) 73 | ) 74 | (layer "F.SilkS") 75 | (uuid "c46a12b3-91b9-4196-8a53-f512d4a50c95") 76 | ) 77 | (fp_line 78 | (start -13.3 4.4) 79 | (end -13.3 5.4) 80 | (stroke 81 | (width 0.1) 82 | (type default) 83 | ) 84 | (layer "F.SilkS") 85 | (uuid "e20bfc5e-1535-4169-b61a-82926bbcfe62") 86 | ) 87 | (fp_line 88 | (start -13.3 5.4) 89 | (end -12.3 5.4) 90 | (stroke 91 | (width 0.1) 92 | (type default) 93 | ) 94 | (layer "F.SilkS") 95 | (uuid "a1095946-fa00-4fe3-ac25-d6c34f2a4d55") 96 | ) 97 | (fp_line 98 | (start 13.3 -6) 99 | (end 12.3 -6) 100 | (stroke 101 | (width 0.1) 102 | (type default) 103 | ) 104 | (layer "F.SilkS") 105 | (uuid "996772e6-402b-431f-9b11-58120d5388e6") 106 | ) 107 | (fp_line 108 | (start 13.3 -5) 109 | (end 13.3 -6) 110 | (stroke 111 | (width 0.1) 112 | (type default) 113 | ) 114 | (layer "F.SilkS") 115 | (uuid "3e3acf72-1328-4158-b56d-f3855b27fc79") 116 | ) 117 | (fp_line 118 | (start 13.3 4.4) 119 | (end 13.3 5.4) 120 | (stroke 121 | (width 0.1) 122 | (type default) 123 | ) 124 | (layer "F.SilkS") 125 | (uuid "14bef9ef-fb94-4117-83f8-ce12b5639270") 126 | ) 127 | (fp_line 128 | (start 13.3 5.4) 129 | (end 12.3 5.4) 130 | (stroke 131 | (width 0.1) 132 | (type default) 133 | ) 134 | (layer "F.SilkS") 135 | (uuid "f8d87ec3-619e-4f8c-8f44-9ae019b579c5") 136 | ) 137 | (fp_line 138 | (start 15.6 5.4) 139 | (end 16.6 5.4) 140 | (stroke 141 | (width 0.1) 142 | (type default) 143 | ) 144 | (layer "F.SilkS") 145 | (uuid "f4f95c0e-ccf7-445c-961a-2181e434fa88") 146 | ) 147 | (fp_line 148 | (start 16.6 -6) 149 | (end 15.6 -6) 150 | (stroke 151 | (width 0.1) 152 | (type default) 153 | ) 154 | (layer "F.SilkS") 155 | (uuid "6bf2ef80-d91a-4b36-b042-688741e7a0e1") 156 | ) 157 | (fp_line 158 | (start 16.6 -5) 159 | (end 16.6 -6) 160 | (stroke 161 | (width 0.1) 162 | (type default) 163 | ) 164 | (layer "F.SilkS") 165 | (uuid "b5f9888d-81a6-45d7-9c22-c6e9b790b618") 166 | ) 167 | (fp_line 168 | (start 16.6 5.4) 169 | (end 16.6 4.4) 170 | (stroke 171 | (width 0.1) 172 | (type default) 173 | ) 174 | (layer "F.SilkS") 175 | (uuid "f686e62b-076a-409b-b49e-6c4185f42450") 176 | ) 177 | (fp_rect 178 | (start -11.19 -3.9325) 179 | (end 11.19 1.6475) 180 | (stroke 181 | (width 0.1) 182 | (type default) 183 | ) 184 | (fill no) 185 | (layer "F.SilkS") 186 | (uuid "e84dce9a-2c88-415c-b078-f35b144cb524") 187 | ) 188 | (fp_line 189 | (start 13.335 -6.0325) 190 | (end 13.335 5.3975) 191 | (stroke 192 | (width 0.1) 193 | (type default) 194 | ) 195 | (layer "Dwgs.User") 196 | (uuid "160db7ce-ad99-473b-8970-65561bb067a5") 197 | ) 198 | (fp_text user "${REFERENCE}" 199 | (at 0 2.5 0) 200 | (unlocked yes) 201 | (layer "F.Fab") 202 | (uuid "a681b434-d47f-4336-ada0-519f98642e56") 203 | (effects 204 | (font 205 | (size 1 1) 206 | (thickness 0.15) 207 | ) 208 | ) 209 | ) 210 | (pad "1" smd rect 211 | (at 12 4.225) 212 | (size 3 0.35) 213 | (layers "B.Cu" "B.Mask" "B.Paste") 214 | (thermal_bridge_angle 45) 215 | (uuid "69ea40da-478a-484d-8015-a7ebfd0abae7") 216 | ) 217 | (pad "2" smd rect 218 | (at 12 3.575) 219 | (size 3 0.35) 220 | (layers "B.Cu" "B.Mask" "B.Paste") 221 | (thermal_bridge_angle 45) 222 | (uuid "49c8c84d-bfe3-4eb0-a490-0241adfdbdfa") 223 | ) 224 | (pad "3" smd rect 225 | (at 12 2.925) 226 | (size 3 0.35) 227 | (layers "B.Cu" "B.Mask" "B.Paste") 228 | (thermal_bridge_angle 45) 229 | (uuid "fbc4f82a-7985-4aa6-850b-c7e31be63718") 230 | ) 231 | (pad "4" smd rect 232 | (at 12 2.275) 233 | (size 3 0.35) 234 | (layers "B.Cu" "B.Mask" "B.Paste") 235 | (thermal_bridge_angle 45) 236 | (uuid "0d17d7d2-159e-46a8-8b36-e0d2add9ff2a") 237 | ) 238 | (pad "5" smd rect 239 | (at 12 1.625) 240 | (size 3 0.35) 241 | (layers "B.Cu" "B.Mask" "B.Paste") 242 | (thermal_bridge_angle 45) 243 | (uuid "ce3ab68f-0514-44de-9fa4-189d2d1ea970") 244 | ) 245 | (pad "6" smd rect 246 | (at 12 0.975) 247 | (size 3 0.35) 248 | (layers "B.Cu" "B.Mask" "B.Paste") 249 | (thermal_bridge_angle 45) 250 | (uuid "d474c3b2-7e70-4e8a-a203-e80a7a607a61") 251 | ) 252 | (pad "7" smd rect 253 | (at 12 0.325) 254 | (size 3 0.35) 255 | (layers "B.Cu" "B.Mask" "B.Paste") 256 | (thermal_bridge_angle 45) 257 | (uuid "faacb815-e20d-42f0-a58d-98008abeaafa") 258 | ) 259 | (pad "8" smd rect 260 | (at 12 -0.325) 261 | (size 3 0.35) 262 | (layers "B.Cu" "B.Mask" "B.Paste") 263 | (thermal_bridge_angle 45) 264 | (uuid "5fbcf406-e952-44bf-a19c-bf3365e10fdd") 265 | ) 266 | (pad "9" smd rect 267 | (at 12 -0.975) 268 | (size 3 0.35) 269 | (layers "B.Cu" "B.Mask" "B.Paste") 270 | (thermal_bridge_angle 45) 271 | (uuid "5f173ab3-0792-4708-9869-77dacd692db8") 272 | ) 273 | (pad "10" smd rect 274 | (at 12 -1.625) 275 | (size 3 0.35) 276 | (layers "B.Cu" "B.Mask" "B.Paste") 277 | (thermal_bridge_angle 45) 278 | (uuid "1abd16c4-5fe6-4e7d-bf44-821360dc2c9f") 279 | ) 280 | (pad "11" smd rect 281 | (at 12 -2.275) 282 | (size 3 0.35) 283 | (layers "B.Cu" "B.Mask" "B.Paste") 284 | (thermal_bridge_angle 45) 285 | (uuid "85d97cc1-698b-48a8-8d8a-5842552de1cb") 286 | ) 287 | (pad "12" smd rect 288 | (at 12 -2.925) 289 | (size 3 0.35) 290 | (layers "B.Cu" "B.Mask" "B.Paste") 291 | (thermal_bridge_angle 45) 292 | (uuid "86441e42-5538-41da-b98b-a0dd4ef20489") 293 | ) 294 | (pad "13" smd rect 295 | (at 12 -3.575) 296 | (size 3 0.35) 297 | (layers "B.Cu" "B.Mask" "B.Paste") 298 | (thermal_bridge_angle 45) 299 | (uuid "d1b2e7ed-4ac9-4ac9-a04a-3d6ce33fe26a") 300 | ) 301 | (pad "14" smd rect 302 | (at 12 -4.225) 303 | (size 3 0.35) 304 | (layers "B.Cu" "B.Mask" "B.Paste") 305 | (thermal_bridge_angle 45) 306 | (uuid "86d83784-cffc-4cef-998b-025c4f4e289d") 307 | ) 308 | (pad "15" smd rect 309 | (at 12 -4.875) 310 | (size 3 0.35) 311 | (layers "B.Cu" "B.Mask" "B.Paste") 312 | (thermal_bridge_angle 45) 313 | (uuid "cbdada57-70ce-41d1-a876-69b911f66a7c") 314 | ) 315 | (embedded_fonts no) 316 | ) 317 | -------------------------------------------------------------------------------- /hardware/board/main.pretty/SW_SPDT_PCM12.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "SW_SPDT_PCM12" 2 | (version 20241229) 3 | (generator "pcbnew") 4 | (generator_version "9.0") 5 | (layer "F.Cu") 6 | (descr "Ultraminiature Surface Mount Slide Switch, right-angle, https://www.ckswitches.com/media/1424/pcm.pdf") 7 | (property "Reference" "REF**" 8 | (at 0 -3.2 0) 9 | (layer "F.SilkS") 10 | (hide yes) 11 | (uuid "2ac20fed-885d-4f04-bac3-60339a77ec8d") 12 | (effects 13 | (font 14 | (size 1 1) 15 | (thickness 0.15) 16 | ) 17 | ) 18 | ) 19 | (property "Value" "SW_SPDT" 20 | (at 0 4.25 0) 21 | (layer "F.Fab") 22 | (uuid "046b6bef-2207-41a7-b2ce-f90ef6779490") 23 | (effects 24 | (font 25 | (size 1 1) 26 | (thickness 0.15) 27 | ) 28 | ) 29 | ) 30 | (property "Datasheet" "" 31 | (at 0 0 180) 32 | (unlocked yes) 33 | (layer "F.Fab") 34 | (hide yes) 35 | (uuid "a6fdd00e-399d-499d-851e-ba6670e3a518") 36 | (effects 37 | (font 38 | (size 1.27 1.27) 39 | (thickness 0.15) 40 | ) 41 | ) 42 | ) 43 | (property "Description" "Switch, single pole double throw" 44 | (at 0 0 180) 45 | (unlocked yes) 46 | (layer "F.Fab") 47 | (hide yes) 48 | (uuid "971f342d-7890-4a5f-a437-a6a69bffee32") 49 | (effects 50 | (font 51 | (size 1.27 1.27) 52 | (thickness 0.15) 53 | ) 54 | ) 55 | ) 56 | (attr smd) 57 | (fp_line 58 | (start 3.45 0.72) 59 | (end 3.45 -0.07) 60 | (stroke 61 | (width 0.12) 62 | (type solid) 63 | ) 64 | (layer "F.SilkS") 65 | (uuid "60c745c5-e849-48b4-9eda-244c6e64236e") 66 | ) 67 | (fp_line 68 | (start 1.4 -1.12) 69 | (end 1.6 -1.12) 70 | (stroke 71 | (width 0.12) 72 | (type solid) 73 | ) 74 | (layer "F.SilkS") 75 | (uuid "2b42aa3d-dc00-4d65-b098-04c74a349152") 76 | ) 77 | (fp_line 78 | (start -0.1 3.02) 79 | (end -0.1 1.73) 80 | (stroke 81 | (width 0.12) 82 | (type solid) 83 | ) 84 | (layer "F.SilkS") 85 | (uuid "ded58701-b4d8-4139-bee2-460efffecb79") 86 | ) 87 | (fp_line 88 | (start -0.1 3.02) 89 | (end -0.3 3.23) 90 | (stroke 91 | (width 0.12) 92 | (type solid) 93 | ) 94 | (layer "F.SilkS") 95 | (uuid "5c721fc0-87f2-454c-b396-ab53e0197cb1") 96 | ) 97 | (fp_line 98 | (start -1.2 3.23) 99 | (end -0.3 3.23) 100 | (stroke 101 | (width 0.12) 102 | (type solid) 103 | ) 104 | (layer "F.SilkS") 105 | (uuid "1a74b481-bd6e-42d1-aa89-a04b60a8af02") 106 | ) 107 | (fp_line 108 | (start -1.4 3.02) 109 | (end -1.2 3.23) 110 | (stroke 111 | (width 0.12) 112 | (type solid) 113 | ) 114 | (layer "F.SilkS") 115 | (uuid "1cbcceca-57a3-4909-b627-45bd809d796a") 116 | ) 117 | (fp_line 118 | (start -1.4 1.73) 119 | (end -1.4 3.02) 120 | (stroke 121 | (width 0.12) 122 | (type solid) 123 | ) 124 | (layer "F.SilkS") 125 | (uuid "ae80a97e-31d8-4eab-8373-4a98db6e47aa") 126 | ) 127 | (fp_line 128 | (start -1.6 -1.12) 129 | (end 0.1 -1.12) 130 | (stroke 131 | (width 0.12) 132 | (type solid) 133 | ) 134 | (layer "F.SilkS") 135 | (uuid "be50062c-6025-4416-961f-b3292f1e1a8c") 136 | ) 137 | (fp_line 138 | (start -2.85 1.73) 139 | (end 2.85 1.73) 140 | (stroke 141 | (width 0.12) 142 | (type solid) 143 | ) 144 | (layer "F.SilkS") 145 | (uuid "a075e8f9-28d5-468f-9a89-5c3cd072542f") 146 | ) 147 | (fp_line 148 | (start -3.45 -0.07) 149 | (end -3.45 0.72) 150 | (stroke 151 | (width 0.12) 152 | (type solid) 153 | ) 154 | (layer "F.SilkS") 155 | (uuid "afc7ee18-e29f-4934-a1a2-1cb218113b02") 156 | ) 157 | (fp_line 158 | (start 4.4 2.1) 159 | (end 1.65 2.1) 160 | (stroke 161 | (width 0.05) 162 | (type solid) 163 | ) 164 | (layer "F.CrtYd") 165 | (uuid "347ece4c-deb5-4df8-b482-3b4e303ff1f8") 166 | ) 167 | (fp_line 168 | (start 4.4 -2.45) 169 | (end 4.4 2.1) 170 | (stroke 171 | (width 0.05) 172 | (type solid) 173 | ) 174 | (layer "F.CrtYd") 175 | (uuid "6e181cd1-2fac-4c1a-8237-ee98b65a5074") 176 | ) 177 | (fp_line 178 | (start 1.65 3.4) 179 | (end -1.65 3.4) 180 | (stroke 181 | (width 0.05) 182 | (type solid) 183 | ) 184 | (layer "F.CrtYd") 185 | (uuid "122c1978-7c8c-44df-8555-1cd8bc654095") 186 | ) 187 | (fp_line 188 | (start 1.65 2.1) 189 | (end 1.65 3.4) 190 | (stroke 191 | (width 0.05) 192 | (type solid) 193 | ) 194 | (layer "F.CrtYd") 195 | (uuid "0d460b9f-53d0-4df1-933d-b1881d7ae0ff") 196 | ) 197 | (fp_line 198 | (start -1.65 3.4) 199 | (end -1.65 2.1) 200 | (stroke 201 | (width 0.05) 202 | (type solid) 203 | ) 204 | (layer "F.CrtYd") 205 | (uuid "9094de83-73cb-42b9-a01d-27e7bd3c5457") 206 | ) 207 | (fp_line 208 | (start -1.65 2.1) 209 | (end -4.4 2.1) 210 | (stroke 211 | (width 0.05) 212 | (type solid) 213 | ) 214 | (layer "F.CrtYd") 215 | (uuid "f3c5e447-f448-4e3a-b476-5afe25af3690") 216 | ) 217 | (fp_line 218 | (start -4.4 2.1) 219 | (end -4.4 -2.45) 220 | (stroke 221 | (width 0.05) 222 | (type solid) 223 | ) 224 | (layer "F.CrtYd") 225 | (uuid "92bcb22b-cc16-42c6-8b32-9c8c8c692133") 226 | ) 227 | (fp_line 228 | (start -4.4 -2.45) 229 | (end 4.4 -2.45) 230 | (stroke 231 | (width 0.05) 232 | (type solid) 233 | ) 234 | (layer "F.CrtYd") 235 | (uuid "cf581a99-931a-416e-8b9b-82690f6ae0d0") 236 | ) 237 | (fp_line 238 | (start 3.35 1.6) 239 | (end 3.35 -1) 240 | (stroke 241 | (width 0.1) 242 | (type solid) 243 | ) 244 | (layer "F.Fab") 245 | (uuid "e31c5a0b-d954-44a1-88c4-3ab336589bda") 246 | ) 247 | (fp_line 248 | (start 3.35 -1) 249 | (end -3.35 -1) 250 | (stroke 251 | (width 0.1) 252 | (type solid) 253 | ) 254 | (layer "F.Fab") 255 | (uuid "7336c96b-3138-47a0-b992-1344f7ea049e") 256 | ) 257 | (fp_line 258 | (start -0.1 2.9) 259 | (end -0.1 1.6) 260 | (stroke 261 | (width 0.1) 262 | (type solid) 263 | ) 264 | (layer "F.Fab") 265 | (uuid "2b9ff040-4b36-405a-8452-ee58c91ff8d2") 266 | ) 267 | (fp_line 268 | (start -0.15 2.95) 269 | (end -0.1 2.9) 270 | (stroke 271 | (width 0.1) 272 | (type solid) 273 | ) 274 | (layer "F.Fab") 275 | (uuid "7a89951d-e094-4561-9194-fd6c409b9bbd") 276 | ) 277 | (fp_line 278 | (start -0.35 3.15) 279 | (end -0.15 2.95) 280 | (stroke 281 | (width 0.1) 282 | (type solid) 283 | ) 284 | (layer "F.Fab") 285 | (uuid "83c6d206-46da-4d7f-95d5-36e8ee30f0e8") 286 | ) 287 | (fp_line 288 | (start -1.2 3.15) 289 | (end -0.35 3.15) 290 | (stroke 291 | (width 0.1) 292 | (type solid) 293 | ) 294 | (layer "F.Fab") 295 | (uuid "a5945874-8910-4377-9fae-38063fd5e925") 296 | ) 297 | (fp_line 298 | (start -1.4 2.95) 299 | (end -1.2 3.15) 300 | (stroke 301 | (width 0.1) 302 | (type solid) 303 | ) 304 | (layer "F.Fab") 305 | (uuid "7db22f1e-0d6b-4b18-9b18-5ca0b4e5e363") 306 | ) 307 | (fp_line 308 | (start -1.4 1.65) 309 | (end -1.4 2.95) 310 | (stroke 311 | (width 0.1) 312 | (type solid) 313 | ) 314 | (layer "F.Fab") 315 | (uuid "42d7c9d4-0b7d-4f4f-8919-32918cd9daec") 316 | ) 317 | (fp_line 318 | (start -3.35 1.6) 319 | (end 3.35 1.6) 320 | (stroke 321 | (width 0.1) 322 | (type solid) 323 | ) 324 | (layer "F.Fab") 325 | (uuid "29db8e64-70b9-40e4-8305-d7b0844b84d8") 326 | ) 327 | (fp_line 328 | (start -3.35 -1) 329 | (end -3.35 1.6) 330 | (stroke 331 | (width 0.1) 332 | (type solid) 333 | ) 334 | (layer "F.Fab") 335 | (uuid "78283039-e86b-4197-970d-e6c82f29c8a7") 336 | ) 337 | (fp_text user "${REFERENCE}" 338 | (at 0 0.222976 90) 339 | (layer "F.Fab") 340 | (uuid "b20888e4-df38-4a91-8990-422fe898591c") 341 | (effects 342 | (font 343 | (size 1 1) 344 | (thickness 0.15) 345 | ) 346 | ) 347 | ) 348 | (pad "" np_thru_hole circle 349 | (at -1.5 0.33 180) 350 | (size 0.9 0.9) 351 | (drill 0.9) 352 | (layers "*.Cu" "*.Mask") 353 | (uuid "0b72dc7e-e2ca-45a3-ad2f-2a03021b8ba2") 354 | ) 355 | (pad "" np_thru_hole circle 356 | (at 1.5 0.33 180) 357 | (size 0.9 0.9) 358 | (drill 0.9) 359 | (layers "*.Cu" "*.Mask") 360 | (uuid "41b25426-4aea-4944-83c0-afe6c1489008") 361 | ) 362 | (pad "1" smd rect 363 | (at -2.25 -1.43 180) 364 | (size 0.7 1.5) 365 | (layers "F.Cu" "F.Mask" "F.Paste") 366 | (uuid "2cc62f03-6b27-4a0c-8bf1-7de86bca80ed") 367 | ) 368 | (pad "2" smd rect 369 | (at 0.75 -1.43 180) 370 | (size 0.7 1.5) 371 | (layers "F.Cu" "F.Mask" "F.Paste") 372 | (uuid "7f93dbc7-6507-4a56-9dd7-d5bcdce956e4") 373 | ) 374 | (pad "3" smd rect 375 | (at 2.25 -1.43 180) 376 | (size 0.7 1.5) 377 | (layers "F.Cu" "F.Mask" "F.Paste") 378 | (uuid "f847b553-2e78-4a72-99a0-c53f6945a276") 379 | ) 380 | (pad "G" smd rect 381 | (at -3.65 -0.78 180) 382 | (size 1 0.8) 383 | (layers "F.Cu" "F.Mask" "F.Paste") 384 | (uuid "af2838b1-73c3-49f0-abe8-ba2523aa7569") 385 | ) 386 | (pad "G" smd rect 387 | (at -3.65 1.43 180) 388 | (size 1 0.8) 389 | (layers "F.Cu" "F.Mask" "F.Paste") 390 | (uuid "c959fb32-05ea-485d-a59c-c89840d5d1fb") 391 | ) 392 | (pad "G" smd rect 393 | (at 3.65 -0.78 180) 394 | (size 1 0.8) 395 | (layers "F.Cu" "F.Mask" "F.Paste") 396 | (uuid "f0382fcb-fc68-40a8-a9bc-7fd02359b9c5") 397 | ) 398 | (pad "G" smd rect 399 | (at 3.65 1.43 180) 400 | (size 1 0.8) 401 | (layers "F.Cu" "F.Mask" "F.Paste") 402 | (uuid "188b6856-2455-4c96-8b90-1e8083bb6158") 403 | ) 404 | (embedded_fonts no) 405 | (model "${KICAD8_3DMODEL_DIR}/Button_Switch_SMD.3dshapes/SW_SPDT_PCM12.wrl" 406 | (offset 407 | (xyz 0 0 0) 408 | ) 409 | (scale 410 | (xyz 1 1 1) 411 | ) 412 | (rotate 413 | (xyz 0 0 0) 414 | ) 415 | ) 416 | ) 417 | -------------------------------------------------------------------------------- /hardware/board/main.pretty/SW_SPST_EVQQ2.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "SW_SPST_EVQQ2" 2 | (version 20241229) 3 | (generator "pcbnew") 4 | (generator_version "9.0") 5 | (layer "F.Cu") 6 | (descr "Light Touch Switch, https://industrial.panasonic.com/cdbs/www-data/pdf/ATK0000/ATK0000CE28.pdf") 7 | (property "Reference" "REF**" 8 | (at 0 0.635 180) 9 | (layer "F.SilkS") 10 | (uuid "246d0d35-8480-49ab-ac58-449dbacad19f") 11 | (effects 12 | (font 13 | (size 1 1) 14 | (thickness 0.15) 15 | ) 16 | ) 17 | ) 18 | (property "Value" "~" 19 | (at 0 4.25 180) 20 | (layer "F.Fab") 21 | (uuid "4dad926d-bba4-4384-820f-d32f30ae7531") 22 | (effects 23 | (font 24 | (size 1 1) 25 | (thickness 0.15) 26 | ) 27 | ) 28 | ) 29 | (property "Datasheet" "" 30 | (at 0 0 0) 31 | (unlocked yes) 32 | (layer "F.Fab") 33 | (hide yes) 34 | (uuid "e28da21b-2c99-4917-a3ef-16bfe3ffc0ec") 35 | (effects 36 | (font 37 | (size 1.27 1.27) 38 | (thickness 0.15) 39 | ) 40 | ) 41 | ) 42 | (property "Description" "Push button switch, generic, two pins" 43 | (at 0 0 0) 44 | (unlocked yes) 45 | (layer "F.Fab") 46 | (hide yes) 47 | (uuid "eb5ba524-5051-4697-ba75-b854f072d1a8") 48 | (effects 49 | (font 50 | (size 1.27 1.27) 51 | (thickness 0.15) 52 | ) 53 | ) 54 | ) 55 | (attr smd) 56 | (fp_line 57 | (start -3.35 -3.1) 58 | (end -3.35 -2.9) 59 | (stroke 60 | (width 0.12) 61 | (type solid) 62 | ) 63 | (layer "F.SilkS") 64 | (uuid "eb74ce6a-7565-4d52-81fe-516416fec4fc") 65 | ) 66 | (fp_line 67 | (start -3.35 -1.2) 68 | (end -3.35 1.2) 69 | (stroke 70 | (width 0.12) 71 | (type solid) 72 | ) 73 | (layer "F.SilkS") 74 | (uuid "09478191-22e1-4e7e-aaf6-c447b3645664") 75 | ) 76 | (fp_line 77 | (start -3.35 3.1) 78 | (end -3.35 2.9) 79 | (stroke 80 | (width 0.12) 81 | (type solid) 82 | ) 83 | (layer "F.SilkS") 84 | (uuid "bc710f31-e23e-4e98-8907-254d2545f1b3") 85 | ) 86 | (fp_line 87 | (start -3.35 3.1) 88 | (end 3.35 3.1) 89 | (stroke 90 | (width 0.12) 91 | (type solid) 92 | ) 93 | (layer "F.SilkS") 94 | (uuid "8651990e-32ac-4c2d-8be2-8e6ee2391f3f") 95 | ) 96 | (fp_line 97 | (start 3.35 -3.1) 98 | (end -3.35 -3.1) 99 | (stroke 100 | (width 0.12) 101 | (type solid) 102 | ) 103 | (layer "F.SilkS") 104 | (uuid "56357eea-c7d1-4068-b290-bda5741de73b") 105 | ) 106 | (fp_line 107 | (start 3.35 -3.1) 108 | (end 3.35 -2.9) 109 | (stroke 110 | (width 0.12) 111 | (type solid) 112 | ) 113 | (layer "F.SilkS") 114 | (uuid "162884da-ec61-4e9f-96a1-5385c4291083") 115 | ) 116 | (fp_line 117 | (start 3.35 -1.2) 118 | (end 3.35 1.2) 119 | (stroke 120 | (width 0.12) 121 | (type solid) 122 | ) 123 | (layer "F.SilkS") 124 | (uuid "4fbc810c-41a2-4e14-bd85-f4c2a8cb9ad5") 125 | ) 126 | (fp_line 127 | (start 3.35 3.1) 128 | (end 3.35 2.9) 129 | (stroke 130 | (width 0.12) 131 | (type solid) 132 | ) 133 | (layer "F.SilkS") 134 | (uuid "597f3550-24e5-4e26-bfc0-128787e6ce9e") 135 | ) 136 | (fp_line 137 | (start -5.25 -3.25) 138 | (end 5.25 -3.25) 139 | (stroke 140 | (width 0.05) 141 | (type solid) 142 | ) 143 | (layer "F.CrtYd") 144 | (uuid "22aa5324-a454-456f-8cb7-c513b06986c1") 145 | ) 146 | (fp_line 147 | (start -5.25 3.25) 148 | (end -5.25 -3.25) 149 | (stroke 150 | (width 0.05) 151 | (type solid) 152 | ) 153 | (layer "F.CrtYd") 154 | (uuid "ad7b5544-c938-45e9-8f56-c583b549c1cc") 155 | ) 156 | (fp_line 157 | (start 5.25 -3.25) 158 | (end 5.25 3.25) 159 | (stroke 160 | (width 0.05) 161 | (type solid) 162 | ) 163 | (layer "F.CrtYd") 164 | (uuid "d0d58b41-a00f-489c-abb2-8be71945bfed") 165 | ) 166 | (fp_line 167 | (start 5.25 3.25) 168 | (end -5.25 3.25) 169 | (stroke 170 | (width 0.05) 171 | (type solid) 172 | ) 173 | (layer "F.CrtYd") 174 | (uuid "bf173c4a-5ae2-4add-80e9-2d44c81fc1b2") 175 | ) 176 | (fp_line 177 | (start -3.25 -3) 178 | (end 3.25 -3) 179 | (stroke 180 | (width 0.1) 181 | (type solid) 182 | ) 183 | (layer "F.Fab") 184 | (uuid "f680a319-8b07-477c-930e-2aa91e91c65a") 185 | ) 186 | (fp_line 187 | (start -3.25 3) 188 | (end -3.25 -3) 189 | (stroke 190 | (width 0.1) 191 | (type solid) 192 | ) 193 | (layer "F.Fab") 194 | (uuid "999330c4-85d7-405c-af41-5eb3b95c580d") 195 | ) 196 | (fp_line 197 | (start 3.25 -3) 198 | (end 3.25 3) 199 | (stroke 200 | (width 0.1) 201 | (type solid) 202 | ) 203 | (layer "F.Fab") 204 | (uuid "f889e913-3622-4a2b-864e-7480717da5cb") 205 | ) 206 | (fp_line 207 | (start 3.25 3) 208 | (end -3.25 3) 209 | (stroke 210 | (width 0.1) 211 | (type solid) 212 | ) 213 | (layer "F.Fab") 214 | (uuid "fec395dd-5385-4dda-945d-326d7ee647af") 215 | ) 216 | (fp_circle 217 | (center 0 0) 218 | (end 1.5 0) 219 | (stroke 220 | (width 0.1) 221 | (type solid) 222 | ) 223 | (fill no) 224 | (layer "F.Fab") 225 | (uuid "863ea975-90ea-4d4d-aba7-890818c0349c") 226 | ) 227 | (fp_circle 228 | (center 0 0) 229 | (end 1.9 0) 230 | (stroke 231 | (width 0.1) 232 | (type solid) 233 | ) 234 | (fill no) 235 | (layer "F.Fab") 236 | (uuid "9df83ce6-52a8-4607-a989-e295fa18a889") 237 | ) 238 | (fp_text user "${REFERENCE}" 239 | (at 0.05 -3.95 180) 240 | (layer "F.Fab") 241 | (uuid "4a34d857-14e8-4810-9e0a-344f401842ef") 242 | (effects 243 | (font 244 | (size 1 1) 245 | (thickness 0.15) 246 | ) 247 | ) 248 | ) 249 | (pad "1" smd rect 250 | (at -2.9 -2) 251 | (size 2.25 1) 252 | (layers "F.Cu" "F.Mask" "F.Paste") 253 | (uuid "24c64fa1-fb2f-47e6-aa74-12ba65e6819c") 254 | ) 255 | (pad "1" smd rect 256 | (at 2.9 -2) 257 | (size 2.25 1) 258 | (layers "F.Cu" "F.Mask" "F.Paste") 259 | (uuid "9b1376be-1b74-4857-a79a-b51373d964df") 260 | ) 261 | (pad "2" smd rect 262 | (at -2.9 2) 263 | (size 2.25 1) 264 | (layers "F.Cu" "F.Mask" "F.Paste") 265 | (uuid "a815d4dc-1f5b-4394-8d16-b86c38a73749") 266 | ) 267 | (pad "2" smd rect 268 | (at 2.9 2) 269 | (size 2.25 1) 270 | (layers "F.Cu" "F.Mask" "F.Paste") 271 | (uuid "2de3714e-204e-4c58-b2e6-b7e24e235be0") 272 | ) 273 | (embedded_fonts no) 274 | (model "${KICAD8_3DMODEL_DIR}/Button_Switch_SMD.3dshapes/SW_SPST_EVQQ2.wrl" 275 | (offset 276 | (xyz 0 0 0) 277 | ) 278 | (scale 279 | (xyz 1 1 1) 280 | ) 281 | (rotate 282 | (xyz 0 0 0) 283 | ) 284 | ) 285 | ) 286 | -------------------------------------------------------------------------------- /hardware/board/main.pretty/USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal" 2 | (version 20241229) 3 | (generator "pcbnew") 4 | (generator_version "9.0") 5 | (layer "F.Cu") 6 | (descr "USB 2.0 Type C Receptacle, GCT, 16P, top mounted, horizontal, 5A: https://gct.co/files/drawings/usb4105.pdf") 7 | (tags "USB C Type-C Receptacle SMD USB 2.0 16P 16C USB4105-15-A USB4105-15-A-060 USB4105-15-A-120 USB4105-GF-A USB4105-GF-A-060 USB4105-GF-A-120") 8 | (property "Reference" "REF**" 9 | (at 0 -5.5 180) 10 | (unlocked yes) 11 | (layer "F.SilkS") 12 | (hide yes) 13 | (uuid "0aa7fd40-9e45-4eab-9aaf-764744d872fc") 14 | (effects 15 | (font 16 | (size 1 1) 17 | (thickness 0.15) 18 | ) 19 | ) 20 | ) 21 | (property "Value" "~" 22 | (at 0 5 180) 23 | (unlocked yes) 24 | (layer "F.Fab") 25 | (uuid "95b608a4-d5f9-4642-95d3-2b6a821b89e7") 26 | (effects 27 | (font 28 | (size 1 1) 29 | (thickness 0.15) 30 | ) 31 | ) 32 | ) 33 | (property "Datasheet" "https://www.usb.org/sites/default/files/documents/usb_type-c.zip" 34 | (at 0 0 180) 35 | (unlocked yes) 36 | (layer "F.Fab") 37 | (hide yes) 38 | (uuid "0aa57351-f339-4a86-82e9-095a185d76b6") 39 | (effects 40 | (font 41 | (size 1.27 1.27) 42 | (thickness 0.15) 43 | ) 44 | ) 45 | ) 46 | (property "Description" "USB 2.0-only 16P Type-C Receptacle connector" 47 | (at 0 0 180) 48 | (unlocked yes) 49 | (layer "F.Fab") 50 | (hide yes) 51 | (uuid "f326a016-a8a4-4088-8cc7-fca9a21f0d08") 52 | (effects 53 | (font 54 | (size 1.27 1.27) 55 | (thickness 0.15) 56 | ) 57 | ) 58 | ) 59 | (attr smd) 60 | (fp_line 61 | (start 4.67 -0.1) 62 | (end 4.67 -1.8) 63 | (stroke 64 | (width 0.12) 65 | (type solid) 66 | ) 67 | (layer "F.SilkS") 68 | (uuid "786864ec-d0f1-4142-82e8-afea15fa7bb8") 69 | ) 70 | (fp_line 71 | (start -4.67 -0.1) 72 | (end -4.67 -1.8) 73 | (stroke 74 | (width 0.12) 75 | (type solid) 76 | ) 77 | (layer "F.SilkS") 78 | (uuid "685c2f7d-d95c-45af-8279-3f5762c7282c") 79 | ) 80 | (fp_line 81 | (start 5 3.675) 82 | (end -5 3.675) 83 | (stroke 84 | (width 0.1) 85 | (type solid) 86 | ) 87 | (layer "Dwgs.User") 88 | (uuid "b4e493fd-c963-4b88-a81d-05c3725cc4fa") 89 | ) 90 | (fp_rect 91 | (start -5.32 -4.76) 92 | (end 5.32 4.18) 93 | (stroke 94 | (width 0.05) 95 | (type solid) 96 | ) 97 | (fill no) 98 | (layer "F.CrtYd") 99 | (uuid "2b42ad0b-f6cf-442d-9c97-b96e50d772fc") 100 | ) 101 | (fp_rect 102 | (start -4.47 -3.675) 103 | (end 4.47 3.675) 104 | (stroke 105 | (width 0.1) 106 | (type solid) 107 | ) 108 | (fill no) 109 | (layer "F.Fab") 110 | (uuid "c8414e33-fd79-486e-8e6b-d1e418c888db") 111 | ) 112 | (fp_text user "${REFERENCE}" 113 | (at 0 0 180) 114 | (unlocked yes) 115 | (layer "F.Fab") 116 | (uuid "153f9439-328a-4c99-9b9b-0e4efbc84510") 117 | (effects 118 | (font 119 | (size 1 1) 120 | (thickness 0.15) 121 | ) 122 | ) 123 | ) 124 | (pad "" np_thru_hole circle 125 | (at -2.89 -2.605 180) 126 | (size 0.65 0.65) 127 | (drill 0.65) 128 | (layers "F&B.Cu" "*.Mask") 129 | (uuid "a243ac82-38a2-4ac9-b134-6dabc17c2629") 130 | ) 131 | (pad "" np_thru_hole circle 132 | (at 2.89 -2.605 180) 133 | (size 0.65 0.65) 134 | (drill 0.65) 135 | (layers "F&B.Cu" "*.Mask") 136 | (uuid "47469fe1-afe7-4d00-9c5b-706a4c374bfd") 137 | ) 138 | (pad "A1" smd roundrect 139 | (at -3.2 -4 180) 140 | (size 0.6 1.6) 141 | (layers "F.Cu" "F.Mask" "F.Paste") 142 | (roundrect_rratio 0.25) 143 | (uuid "d377bf38-7a34-482c-b05e-f08db655495e") 144 | ) 145 | (pad "A4" smd roundrect 146 | (at -2.4 -4 180) 147 | (size 0.6 1.6) 148 | (layers "F.Cu" "F.Mask" "F.Paste") 149 | (roundrect_rratio 0.25) 150 | (uuid "c5948b7e-56cf-4f4d-b021-4187d3881882") 151 | ) 152 | (pad "A5" smd roundrect 153 | (at -1.25 -4 180) 154 | (size 0.3 1.6) 155 | (layers "F.Cu" "F.Mask" "F.Paste") 156 | (roundrect_rratio 0.25) 157 | (uuid "b1060fe6-e58a-4277-b53d-287c6ebed2d4") 158 | ) 159 | (pad "A6" smd roundrect 160 | (at -0.25 -4 180) 161 | (size 0.3 1.6) 162 | (layers "F.Cu" "F.Mask" "F.Paste") 163 | (roundrect_rratio 0.25) 164 | (uuid "9d51d4b5-7f34-421c-9c98-a50841a5f98d") 165 | ) 166 | (pad "A7" smd roundrect 167 | (at 0.25 -4 180) 168 | (size 0.3 1.6) 169 | (layers "F.Cu" "F.Mask" "F.Paste") 170 | (roundrect_rratio 0.25) 171 | (uuid "e61cb0b4-a93f-43a6-9eae-d72c68930746") 172 | ) 173 | (pad "A8" smd roundrect 174 | (at 1.25 -4 180) 175 | (size 0.3 1.6) 176 | (layers "F.Cu" "F.Mask" "F.Paste") 177 | (roundrect_rratio 0.25) 178 | (uuid "07c7ed3d-a1ca-45cf-85bb-f1da5cdab814") 179 | ) 180 | (pad "A9" smd roundrect 181 | (at 2.4 -4 180) 182 | (size 0.6 1.6) 183 | (layers "F.Cu" "F.Mask" "F.Paste") 184 | (roundrect_rratio 0.25) 185 | (uuid "d7ea6668-f5be-4f08-809c-dd792025a48c") 186 | ) 187 | (pad "A12" smd roundrect 188 | (at 3.2 -4 180) 189 | (size 0.6 1.6) 190 | (layers "F.Cu" "F.Mask" "F.Paste") 191 | (roundrect_rratio 0.25) 192 | (uuid "4c64bade-c556-4e0e-8e4b-3968e8c507cf") 193 | ) 194 | (pad "B1" smd roundrect 195 | (at 3.2 -4 180) 196 | (size 0.6 1.6) 197 | (layers "F.Cu" "F.Mask" "F.Paste") 198 | (roundrect_rratio 0.25) 199 | (uuid "ef94461c-7c0c-4e8a-a6df-64035a590cc3") 200 | ) 201 | (pad "B4" smd roundrect 202 | (at 2.4 -4 180) 203 | (size 0.6 1.6) 204 | (layers "F.Cu" "F.Mask" "F.Paste") 205 | (roundrect_rratio 0.25) 206 | (uuid "e9dbf889-833d-44c0-a991-545c2a6a7aa7") 207 | ) 208 | (pad "B5" smd roundrect 209 | (at 1.75 -4 180) 210 | (size 0.3 1.6) 211 | (layers "F.Cu" "F.Mask" "F.Paste") 212 | (roundrect_rratio 0.25) 213 | (uuid "b3d97210-a140-45bc-8ca7-f1cb635dd8c4") 214 | ) 215 | (pad "B6" smd roundrect 216 | (at 0.75 -4 180) 217 | (size 0.3 1.6) 218 | (layers "F.Cu" "F.Mask" "F.Paste") 219 | (roundrect_rratio 0.25) 220 | (uuid "fd3562b7-fef1-4621-900f-86e051869517") 221 | ) 222 | (pad "B7" smd roundrect 223 | (at -0.75 -4 180) 224 | (size 0.3 1.6) 225 | (layers "F.Cu" "F.Mask" "F.Paste") 226 | (roundrect_rratio 0.25) 227 | (uuid "b321b444-3f10-4e6d-849f-39b87efee978") 228 | ) 229 | (pad "B8" smd roundrect 230 | (at -1.75 -4 180) 231 | (size 0.3 1.6) 232 | (layers "F.Cu" "F.Mask" "F.Paste") 233 | (roundrect_rratio 0.25) 234 | (uuid "7bb3d10c-d74f-4913-891f-d8c205203ace") 235 | ) 236 | (pad "B9" smd roundrect 237 | (at -2.4 -4 180) 238 | (size 0.6 1.6) 239 | (layers "F.Cu" "F.Mask" "F.Paste") 240 | (roundrect_rratio 0.25) 241 | (uuid "799ec8cc-249a-4d34-9e66-f0caf7e75610") 242 | ) 243 | (pad "B12" smd roundrect 244 | (at -3.2 -4 180) 245 | (size 0.6 1.6) 246 | (layers "F.Cu" "F.Mask" "F.Paste") 247 | (roundrect_rratio 0.25) 248 | (uuid "a5d38a63-5123-4772-8655-0239c29d8f77") 249 | ) 250 | (pad "S1" thru_hole oval 251 | (at -4.32 -3.105 180) 252 | (size 1.2 2.3) 253 | (drill oval 0.6 1.7) 254 | (layers "*.Cu" "*.Mask" "F.Paste") 255 | (remove_unused_layers no) 256 | (uuid "4d0adf5e-d6f1-4777-9d97-05db5a06eb40") 257 | ) 258 | (pad "S1" thru_hole oval 259 | (at -4.32 1.075 180) 260 | (size 1.2 2) 261 | (drill oval 0.6 1.4) 262 | (layers "*.Cu" "*.Mask" "F.Paste") 263 | (remove_unused_layers no) 264 | (uuid "613097f0-86f8-467c-a30b-74d0f27d715a") 265 | ) 266 | (pad "S1" thru_hole oval 267 | (at 4.32 -3.105 180) 268 | (size 1.2 2.3) 269 | (drill oval 0.6 1.7) 270 | (layers "*.Cu" "*.Mask" "F.Paste") 271 | (remove_unused_layers no) 272 | (uuid "68c3de22-e0ea-45fa-9147-6707106e2ee0") 273 | ) 274 | (pad "S1" thru_hole oval 275 | (at 4.32 1.075 180) 276 | (size 1.2 2) 277 | (drill oval 0.6 1.4) 278 | (layers "*.Cu" "*.Mask" "F.Paste") 279 | (remove_unused_layers no) 280 | (uuid "31a3317d-235a-46f7-8f6e-45cef1d378b4") 281 | ) 282 | (embedded_fonts no) 283 | (model "${KICAD8_3DMODEL_DIR}/Connector_USB.3dshapes/USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal.wrl" 284 | (offset 285 | (xyz 0 0 0) 286 | ) 287 | (scale 288 | (xyz 1 1 1) 289 | ) 290 | (rotate 291 | (xyz 0 0 0) 292 | ) 293 | ) 294 | ) 295 | -------------------------------------------------------------------------------- /hardware/board/sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | (version 7) 3 | (lib (name "main")(type "KiCad")(uri "${KIPRJMOD}/main.kicad_sym")(options "")(descr "")) 4 | ) 5 | -------------------------------------------------------------------------------- /hardware/enclosure-text/main.kicad_pcb: -------------------------------------------------------------------------------- 1 | (kicad_pcb 2 | (version 20241229) 3 | (generator "pcbnew") 4 | (generator_version "9.0") 5 | (general 6 | (thickness 0.806) 7 | (legacy_teardrops no) 8 | ) 9 | (paper "A4") 10 | (layers 11 | (0 "F.Cu" signal) 12 | (4 "In1.Cu" signal) 13 | (6 "In2.Cu" signal) 14 | (2 "B.Cu" signal) 15 | (9 "F.Adhes" user "F.Adhesive") 16 | (11 "B.Adhes" user "B.Adhesive") 17 | (13 "F.Paste" user) 18 | (15 "B.Paste" user) 19 | (5 "F.SilkS" user "F.Silkscreen") 20 | (7 "B.SilkS" user "B.Silkscreen") 21 | (1 "F.Mask" user) 22 | (3 "B.Mask" user) 23 | (17 "Dwgs.User" user "User.Drawings") 24 | (19 "Cmts.User" user "User.Comments") 25 | (21 "Eco1.User" user "User.Eco1") 26 | (23 "Eco2.User" user "User.Eco2") 27 | (25 "Edge.Cuts" user) 28 | (27 "Margin" user) 29 | (31 "F.CrtYd" user "F.Courtyard") 30 | (29 "B.CrtYd" user "B.Courtyard") 31 | (35 "F.Fab" user) 32 | (33 "B.Fab" user) 33 | (39 "User.1" user) 34 | (41 "User.2" user) 35 | (43 "User.3" user) 36 | (45 "User.4" user) 37 | (47 "User.5" user) 38 | (49 "User.6" user) 39 | (51 "User.7" user) 40 | (53 "User.8" user) 41 | (55 "User.9" user) 42 | ) 43 | (setup 44 | (stackup 45 | (layer "F.SilkS" 46 | (type "Top Silk Screen") 47 | ) 48 | (layer "F.Paste" 49 | (type "Top Solder Paste") 50 | ) 51 | (layer "F.Mask" 52 | (type "Top Solder Mask") 53 | (thickness 0.01) 54 | ) 55 | (layer "F.Cu" 56 | (type "copper") 57 | (thickness 0.04) 58 | ) 59 | (layer "dielectric 1" 60 | (type "prepreg") 61 | (thickness 0.138) 62 | (material "FR4") 63 | (epsilon_r 4.5) 64 | (loss_tangent 0.02) 65 | ) 66 | (layer "In1.Cu" 67 | (type "copper") 68 | (thickness 0.035) 69 | ) 70 | (layer "dielectric 2" 71 | (type "core") 72 | (thickness 0.36) 73 | (material "FR4") 74 | (epsilon_r 4.5) 75 | (loss_tangent 0.02) 76 | ) 77 | (layer "In2.Cu" 78 | (type "copper") 79 | (thickness 0.035) 80 | ) 81 | (layer "dielectric 3" 82 | (type "prepreg") 83 | (thickness 0.138) 84 | (material "FR4") 85 | (epsilon_r 4.5) 86 | (loss_tangent 0.02) 87 | ) 88 | (layer "B.Cu" 89 | (type "copper") 90 | (thickness 0.04) 91 | ) 92 | (layer "B.Mask" 93 | (type "Bottom Solder Mask") 94 | (thickness 0.01) 95 | ) 96 | (layer "B.Paste" 97 | (type "Bottom Solder Paste") 98 | ) 99 | (layer "B.SilkS" 100 | (type "Bottom Silk Screen") 101 | ) 102 | (copper_finish "None") 103 | (dielectric_constraints no) 104 | ) 105 | (pad_to_mask_clearance 0) 106 | (allow_soldermask_bridges_in_footprints no) 107 | (tenting front back) 108 | (aux_axis_origin 50.8 50.8) 109 | (grid_origin 50.8 50.8) 110 | (pcbplotparams 111 | (layerselection 0x00000000_00000000_55d55555_55555500) 112 | (plot_on_all_layers_selection 0x00000000_00000000_00000000_00000000) 113 | (disableapertmacros no) 114 | (usegerberextensions yes) 115 | (usegerberattributes yes) 116 | (usegerberadvancedattributes yes) 117 | (creategerberjobfile no) 118 | (dashed_line_dash_ratio 12.000000) 119 | (dashed_line_gap_ratio 3.000000) 120 | (svgprecision 4) 121 | (plotframeref no) 122 | (mode 1) 123 | (useauxorigin yes) 124 | (hpglpennumber 1) 125 | (hpglpenspeed 20) 126 | (hpglpendiameter 15.000000) 127 | (pdf_front_fp_property_popups yes) 128 | (pdf_back_fp_property_popups yes) 129 | (pdf_metadata yes) 130 | (pdf_single_document no) 131 | (dxfpolygonmode yes) 132 | (dxfimperialunits yes) 133 | (dxfusepcbnewfont yes) 134 | (psnegative no) 135 | (psa4output no) 136 | (plot_black_and_white yes) 137 | (plotinvisibletext no) 138 | (sketchpadsonfab no) 139 | (plotpadnumbers no) 140 | (hidednponfab no) 141 | (sketchdnponfab yes) 142 | (crossoutdnponfab yes) 143 | (subtractmaskfromsilk yes) 144 | (outputformat 1) 145 | (mirror no) 146 | (drillshape 0) 147 | (scaleselection 1) 148 | (outputdirectory "output/") 149 | ) 150 | ) 151 | (net 0 "") 152 | (gr_circle 153 | (center 68.58 56.515) 154 | (end 70.485 56.515) 155 | (stroke 156 | (width 0.1) 157 | (type default) 158 | ) 159 | (fill no) 160 | (layer "F.SilkS") 161 | (uuid "006a0931-799c-44a0-a1a6-f46d209f670e") 162 | ) 163 | (gr_circle 164 | (center 46.355 64.135) 165 | (end 48.26 64.135) 166 | (stroke 167 | (width 0.1) 168 | (type default) 169 | ) 170 | (fill no) 171 | (layer "F.SilkS") 172 | (uuid "03d98835-6da7-4ea2-818d-d20921578369") 173 | ) 174 | (gr_circle 175 | (center 33.02 56.515) 176 | (end 34.925 56.515) 177 | (stroke 178 | (width 0.1) 179 | (type default) 180 | ) 181 | (fill no) 182 | (layer "F.SilkS") 183 | (uuid "04336c8c-65a9-4d15-9ab9-3823a15a87d9") 184 | ) 185 | (gr_circle 186 | (center 37.465 64.135) 187 | (end 39.37 64.135) 188 | (stroke 189 | (width 0.1) 190 | (type default) 191 | ) 192 | (fill no) 193 | (layer "F.SilkS") 194 | (uuid "056ba723-8ee3-4c65-9fcb-00a72bd06e15") 195 | ) 196 | (gr_circle 197 | (center 50.8 48.895) 198 | (end 52.705 48.895) 199 | (stroke 200 | (width 0.1) 201 | (type default) 202 | ) 203 | (fill no) 204 | (layer "F.SilkS") 205 | (uuid "11f5d095-3882-4f5a-b50d-2281fe3c7944") 206 | ) 207 | (gr_circle 208 | (center 59.69 56.515) 209 | (end 61.595 56.515) 210 | (stroke 211 | (width 0.1) 212 | (type default) 213 | ) 214 | (fill no) 215 | (layer "F.SilkS") 216 | (uuid "249e31f8-0a50-4598-8423-f5ef2ee4a641") 217 | ) 218 | (gr_circle 219 | (center 64.135 64.135) 220 | (end 66.04 64.135) 221 | (stroke 222 | (width 0.1) 223 | (type default) 224 | ) 225 | (fill no) 226 | (layer "F.SilkS") 227 | (uuid "32c4edd8-b43e-45ae-a646-125c5558845a") 228 | ) 229 | (gr_circle 230 | (center 33.02 48.895) 231 | (end 34.925 48.895) 232 | (stroke 233 | (width 0.1) 234 | (type default) 235 | ) 236 | (fill no) 237 | (layer "F.SilkS") 238 | (uuid "39a012a7-6c13-4594-b4ba-c10ef1ba3472") 239 | ) 240 | (gr_circle 241 | (center 55.245 64.135) 242 | (end 57.15 64.135) 243 | (stroke 244 | (width 0.1) 245 | (type default) 246 | ) 247 | (fill no) 248 | (layer "F.SilkS") 249 | (uuid "50647469-67b6-409e-9c27-3f24922938d7") 250 | ) 251 | (gr_circle 252 | (center 68.58 48.895) 253 | (end 70.485 48.895) 254 | (stroke 255 | (width 0.1) 256 | (type default) 257 | ) 258 | (fill no) 259 | (layer "F.SilkS") 260 | (uuid "79f429b6-90f5-489c-8197-dccb063c8b9e") 261 | ) 262 | (gr_circle 263 | (center 50.8 56.515) 264 | (end 52.705 56.515) 265 | (stroke 266 | (width 0.1) 267 | (type default) 268 | ) 269 | (fill no) 270 | (layer "F.SilkS") 271 | (uuid "91549037-934c-4bf4-97f0-1c94576c08ba") 272 | ) 273 | (gr_circle 274 | (center 59.69 48.895) 275 | (end 61.595 48.895) 276 | (stroke 277 | (width 0.1) 278 | (type default) 279 | ) 280 | (fill no) 281 | (layer "F.SilkS") 282 | (uuid "9f55c862-3a68-4f64-80c2-1a106a8d8df6") 283 | ) 284 | (gr_circle 285 | (center 41.91 56.515) 286 | (end 43.815 56.515) 287 | (stroke 288 | (width 0.1) 289 | (type default) 290 | ) 291 | (fill no) 292 | (layer "F.SilkS") 293 | (uuid "adca83d3-ea06-4650-b66c-ba697fe7a9e3") 294 | ) 295 | (gr_circle 296 | (center 41.91 48.895) 297 | (end 43.815 48.895) 298 | (stroke 299 | (width 0.1) 300 | (type default) 301 | ) 302 | (fill no) 303 | (layer "F.SilkS") 304 | (uuid "dc0471d2-b19f-40d8-9111-164c11f61245") 305 | ) 306 | (gr_line 307 | (start 28.575 34.925) 308 | (end 28.575 66.04) 309 | (stroke 310 | (width 0.05) 311 | (type default) 312 | ) 313 | (layer "Edge.Cuts") 314 | (uuid "0ee0b050-2a8f-4ce0-9753-cd081534c93d") 315 | ) 316 | (gr_arc 317 | (start 28.575 34.925) 318 | (mid 29.132962 33.577962) 319 | (end 30.48 33.02) 320 | (stroke 321 | (width 0.05) 322 | (type default) 323 | ) 324 | (layer "Edge.Cuts") 325 | (uuid "19790e60-0d74-4410-812a-72905d9940f8") 326 | ) 327 | (gr_line 328 | (start 30.48 33.02) 329 | (end 69.215 33.02) 330 | (stroke 331 | (width 0.05) 332 | (type default) 333 | ) 334 | (layer "Edge.Cuts") 335 | (uuid "3b0e2bef-53fd-4544-a8c6-4f3b565af610") 336 | ) 337 | (gr_arc 338 | (start 71.12 44.7675) 339 | (mid 72.467038 45.325462) 340 | (end 73.025 46.6725) 341 | (stroke 342 | (width 0.05) 343 | (type default) 344 | ) 345 | (layer "Edge.Cuts") 346 | (uuid "55a0d725-97dd-4390-b525-10de48f77499") 347 | ) 348 | (gr_arc 349 | (start 30.48 67.945) 350 | (mid 29.132962 67.387038) 351 | (end 28.575 66.04) 352 | (stroke 353 | (width 0.05) 354 | (type default) 355 | ) 356 | (layer "Edge.Cuts") 357 | (uuid "591960be-64b2-40f1-b2ed-ef03400078a7") 358 | ) 359 | (gr_line 360 | (start 73.025 66.04) 361 | (end 73.025 46.6725) 362 | (stroke 363 | (width 0.05) 364 | (type default) 365 | ) 366 | (layer "Edge.Cuts") 367 | (uuid "5ff73f9e-652d-4c6f-b4dd-ca0e1eaf5ff7") 368 | ) 369 | (gr_line 370 | (start 71.12 44.7675) 371 | (end 71.12 34.925) 372 | (stroke 373 | (width 0.05) 374 | (type default) 375 | ) 376 | (layer "Edge.Cuts") 377 | (uuid "744c0861-9b0f-4188-a265-73ab750d3b5b") 378 | ) 379 | (gr_line 380 | (start 30.48 67.945) 381 | (end 71.12 67.945) 382 | (stroke 383 | (width 0.05) 384 | (type default) 385 | ) 386 | (layer "Edge.Cuts") 387 | (uuid "9c33dcee-28c8-44b4-87d2-5e826e272c8f") 388 | ) 389 | (gr_arc 390 | (start 73.025 66.04) 391 | (mid 72.467038 67.387038) 392 | (end 71.12 67.945) 393 | (stroke 394 | (width 0.05) 395 | (type default) 396 | ) 397 | (layer "Edge.Cuts") 398 | (uuid "b765a063-7a14-40ec-b2f2-eac1a68a73e4") 399 | ) 400 | (gr_arc 401 | (start 69.215 33.02) 402 | (mid 70.562038 33.577962) 403 | (end 71.12 34.925) 404 | (stroke 405 | (width 0.05) 406 | (type default) 407 | ) 408 | (layer "Edge.Cuts") 409 | (uuid "e2695f91-f4d0-4675-9aab-be399717e574") 410 | ) 411 | (gr_text "M" 412 | (at 64.135 60.325 0) 413 | (layer "User.9") 414 | (uuid "09c31bb5-3c10-473e-af4e-d20d0aaf0001") 415 | (effects 416 | (font 417 | (size 2 2) 418 | (thickness 0.2) 419 | ) 420 | ) 421 | ) 422 | (gr_text "J K" 423 | (at 59.69 52.705 0) 424 | (layer "User.9") 425 | (uuid "169a545a-4909-4ffd-837a-d9545a34d678") 426 | (effects 427 | (font 428 | (size 2 2) 429 | (thickness 0.2) 430 | ) 431 | ) 432 | ) 433 | (gr_text "U I" 434 | (at 59.69 45.085 0) 435 | (layer "User.9") 436 | (uuid "1b59de5c-913b-4a9d-8e44-a5fe9bf47ea1") 437 | (effects 438 | (font 439 | (size 2 2) 440 | (thickness 0.2) 441 | ) 442 | ) 443 | ) 444 | (gr_text "T Y" 445 | (at 50.8 45.085 0) 446 | (layer "User.9") 447 | (uuid "47092b27-e0e8-45b6-8b76-5fe7a496ad9c") 448 | (effects 449 | (font 450 | (size 2 2) 451 | (thickness 0.2) 452 | ) 453 | ) 454 | ) 455 | (gr_text "Q W" 456 | (at 33.02 45.085 0) 457 | (layer "User.9") 458 | (uuid "4ecab48c-e0f5-477b-8644-0ce0b9df0036") 459 | (effects 460 | (font 461 | (size 2 2) 462 | (thickness 0.2) 463 | ) 464 | ) 465 | ) 466 | (gr_text "G H" 467 | (at 50.8 52.705 0) 468 | (layer "User.9") 469 | (uuid "5f58cce9-82df-46ee-8aa1-209ac5615aac") 470 | (effects 471 | (font 472 | (size 2 2) 473 | (thickness 0.2) 474 | ) 475 | ) 476 | ) 477 | (gr_text "C V" 478 | (at 46.355 60.325 0) 479 | (layer "User.9") 480 | (uuid "60166272-77c6-43e2-bf95-68718df1f578") 481 | (effects 482 | (font 483 | (size 2 2) 484 | (thickness 0.2) 485 | ) 486 | ) 487 | ) 488 | (gr_text "L" 489 | (at 68.58 52.705 0) 490 | (layer "User.9") 491 | (uuid "74b8cc96-e931-431f-9d77-f0df6be1aca0") 492 | (effects 493 | (font 494 | (size 2 2) 495 | (thickness 0.2) 496 | ) 497 | ) 498 | ) 499 | (gr_text "B N" 500 | (at 55.245 60.325 0) 501 | (layer "User.9") 502 | (uuid "7efa3aab-ec1e-48a4-b572-5631ff7ec4fd") 503 | (effects 504 | (font 505 | (size 2 2) 506 | (thickness 0.2) 507 | ) 508 | ) 509 | ) 510 | (gr_text "D F" 511 | (at 41.91 52.705 0) 512 | (layer "User.9") 513 | (uuid "84730c8f-d253-4f2d-838e-c0b82c4ce7c9") 514 | (effects 515 | (font 516 | (size 2 2) 517 | (thickness 0.2) 518 | ) 519 | ) 520 | ) 521 | (gr_text "E R" 522 | (at 41.91 45.085 0) 523 | (layer "User.9") 524 | (uuid "9cf9b32a-eb9e-44f4-922c-fc1373118bf7") 525 | (effects 526 | (font 527 | (size 2 2) 528 | (thickness 0.2) 529 | ) 530 | ) 531 | ) 532 | (gr_text "A S" 533 | (at 33.02 52.705 0) 534 | (layer "User.9") 535 | (uuid "a0730161-30a4-45c1-b7aa-abe55e360c3a") 536 | (effects 537 | (font 538 | (size 2 2) 539 | (thickness 0.2) 540 | ) 541 | ) 542 | ) 543 | (gr_text "Z X" 544 | (at 37.465 60.325 0) 545 | (layer "User.9") 546 | (uuid "c0aa1e6e-cf66-4052-8909-81c6c8561c02") 547 | (effects 548 | (font 549 | (size 2 2) 550 | (thickness 0.2) 551 | ) 552 | ) 553 | ) 554 | (gr_text "O P" 555 | (at 68.58 45.085 0) 556 | (layer "User.9") 557 | (uuid "c97575ef-3fca-4e65-9462-e0b9d05f9f29") 558 | (effects 559 | (font 560 | (size 2 2) 561 | (thickness 0.2) 562 | ) 563 | ) 564 | ) 565 | (embedded_fonts no) 566 | ) 567 | -------------------------------------------------------------------------------- /hardware/enclosure-text/main.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "3dviewports": [], 4 | "design_settings": { 5 | "defaults": { 6 | "apply_defaults_to_fp_fields": false, 7 | "apply_defaults_to_fp_shapes": false, 8 | "apply_defaults_to_fp_text": false, 9 | "board_outline_line_width": 0.05, 10 | "copper_line_width": 0.2, 11 | "copper_text_italic": false, 12 | "copper_text_size_h": 1.5, 13 | "copper_text_size_v": 1.5, 14 | "copper_text_thickness": 0.3, 15 | "copper_text_upright": false, 16 | "courtyard_line_width": 0.05, 17 | "dimension_precision": 4, 18 | "dimension_units": 3, 19 | "dimensions": { 20 | "arrow_length": 1270000, 21 | "extension_offset": 500000, 22 | "keep_text_aligned": true, 23 | "suppress_zeroes": false, 24 | "text_position": 0, 25 | "units_format": 1 26 | }, 27 | "fab_line_width": 0.1, 28 | "fab_text_italic": false, 29 | "fab_text_size_h": 1.0, 30 | "fab_text_size_v": 1.0, 31 | "fab_text_thickness": 0.15, 32 | "fab_text_upright": false, 33 | "other_line_width": 0.1, 34 | "other_text_italic": false, 35 | "other_text_size_h": 1.0, 36 | "other_text_size_v": 1.0, 37 | "other_text_thickness": 0.15, 38 | "other_text_upright": false, 39 | "pads": { 40 | "drill": 0.0, 41 | "height": 1.0, 42 | "width": 1.0 43 | }, 44 | "silk_line_width": 0.1, 45 | "silk_text_italic": false, 46 | "silk_text_size_h": 1.0, 47 | "silk_text_size_v": 1.0, 48 | "silk_text_thickness": 0.1, 49 | "silk_text_upright": false, 50 | "zones": { 51 | "min_clearance": 0.2 52 | } 53 | }, 54 | "diff_pair_dimensions": [ 55 | { 56 | "gap": 0.0, 57 | "via_gap": 0.0, 58 | "width": 0.0 59 | } 60 | ], 61 | "drc_exclusions": [], 62 | "meta": { 63 | "version": 2 64 | }, 65 | "rule_severities": { 66 | "annular_width": "error", 67 | "clearance": "error", 68 | "connection_width": "warning", 69 | "copper_edge_clearance": "error", 70 | "copper_sliver": "warning", 71 | "courtyards_overlap": "error", 72 | "creepage": "error", 73 | "diff_pair_gap_out_of_range": "error", 74 | "diff_pair_uncoupled_length_too_long": "error", 75 | "drill_out_of_range": "error", 76 | "duplicate_footprints": "warning", 77 | "extra_footprint": "warning", 78 | "footprint": "error", 79 | "footprint_filters_mismatch": "ignore", 80 | "footprint_symbol_mismatch": "warning", 81 | "footprint_type_mismatch": "ignore", 82 | "hole_clearance": "error", 83 | "hole_near_hole": "error", 84 | "hole_to_hole": "error", 85 | "holes_co_located": "warning", 86 | "invalid_outline": "error", 87 | "isolated_copper": "warning", 88 | "item_on_disabled_layer": "error", 89 | "items_not_allowed": "error", 90 | "length_out_of_range": "error", 91 | "lib_footprint_issues": "warning", 92 | "lib_footprint_mismatch": "warning", 93 | "malformed_courtyard": "error", 94 | "microvia_drill_out_of_range": "error", 95 | "mirrored_text_on_front_layer": "warning", 96 | "missing_courtyard": "ignore", 97 | "missing_footprint": "warning", 98 | "net_conflict": "warning", 99 | "nonmirrored_text_on_back_layer": "warning", 100 | "npth_inside_courtyard": "ignore", 101 | "padstack": "warning", 102 | "pth_inside_courtyard": "ignore", 103 | "shorting_items": "error", 104 | "silk_edge_clearance": "warning", 105 | "silk_over_copper": "warning", 106 | "silk_overlap": "warning", 107 | "skew_out_of_range": "error", 108 | "solder_mask_bridge": "error", 109 | "starved_thermal": "error", 110 | "text_height": "warning", 111 | "text_thickness": "warning", 112 | "through_hole_pad_without_hole": "error", 113 | "too_many_vias": "error", 114 | "track_angle": "error", 115 | "track_dangling": "warning", 116 | "track_segment_length": "error", 117 | "track_width": "error", 118 | "tracks_crossing": "error", 119 | "unconnected_items": "error", 120 | "unresolved_variable": "error", 121 | "via_dangling": "warning", 122 | "zones_intersect": "error" 123 | }, 124 | "rules": { 125 | "max_error": 0.005, 126 | "min_clearance": 0.2, 127 | "min_connection": 0.0, 128 | "min_copper_edge_clearance": 0.3, 129 | "min_groove_width": 0.0, 130 | "min_hole_clearance": 0.25, 131 | "min_hole_to_hole": 0.25, 132 | "min_microvia_diameter": 0.2, 133 | "min_microvia_drill": 0.1, 134 | "min_resolved_spokes": 2, 135 | "min_silk_clearance": 0.0, 136 | "min_text_height": 0.8, 137 | "min_text_thickness": 0.08, 138 | "min_through_hole_diameter": 0.3, 139 | "min_track_width": 0.3, 140 | "min_via_annular_width": 0.1, 141 | "min_via_diameter": 0.4, 142 | "solder_mask_to_copper_clearance": 0.0, 143 | "use_height_for_length_calcs": true 144 | }, 145 | "teardrop_options": [ 146 | { 147 | "td_onpthpad": true, 148 | "td_onroundshapesonly": false, 149 | "td_onsmdpad": true, 150 | "td_ontrackend": false, 151 | "td_onvia": true 152 | } 153 | ], 154 | "teardrop_parameters": [ 155 | { 156 | "td_allow_use_two_tracks": true, 157 | "td_curve_segcount": 0, 158 | "td_height_ratio": 1.0, 159 | "td_length_ratio": 0.5, 160 | "td_maxheight": 2.0, 161 | "td_maxlen": 1.0, 162 | "td_on_pad_in_zone": false, 163 | "td_target_name": "td_round_shape", 164 | "td_width_to_size_filter_ratio": 0.9 165 | }, 166 | { 167 | "td_allow_use_two_tracks": true, 168 | "td_curve_segcount": 0, 169 | "td_height_ratio": 1.0, 170 | "td_length_ratio": 0.5, 171 | "td_maxheight": 2.0, 172 | "td_maxlen": 1.0, 173 | "td_on_pad_in_zone": false, 174 | "td_target_name": "td_rect_shape", 175 | "td_width_to_size_filter_ratio": 0.9 176 | }, 177 | { 178 | "td_allow_use_two_tracks": true, 179 | "td_curve_segcount": 0, 180 | "td_height_ratio": 1.0, 181 | "td_length_ratio": 0.5, 182 | "td_maxheight": 2.0, 183 | "td_maxlen": 1.0, 184 | "td_on_pad_in_zone": false, 185 | "td_target_name": "td_track_end", 186 | "td_width_to_size_filter_ratio": 0.9 187 | } 188 | ], 189 | "track_widths": [ 190 | 0.0 191 | ], 192 | "tuning_pattern_settings": { 193 | "diff_pair_defaults": { 194 | "corner_radius_percentage": 80, 195 | "corner_style": 1, 196 | "max_amplitude": 1.0, 197 | "min_amplitude": 0.2, 198 | "single_sided": false, 199 | "spacing": 1.0 200 | }, 201 | "diff_pair_skew_defaults": { 202 | "corner_radius_percentage": 80, 203 | "corner_style": 1, 204 | "max_amplitude": 1.0, 205 | "min_amplitude": 0.2, 206 | "single_sided": false, 207 | "spacing": 0.6 208 | }, 209 | "single_track_defaults": { 210 | "corner_radius_percentage": 80, 211 | "corner_style": 1, 212 | "max_amplitude": 1.0, 213 | "min_amplitude": 0.2, 214 | "single_sided": false, 215 | "spacing": 0.6 216 | } 217 | }, 218 | "via_dimensions": [ 219 | { 220 | "diameter": 0.0, 221 | "drill": 0.0 222 | } 223 | ], 224 | "zones_allow_external_fillets": false 225 | }, 226 | "ipc2581": { 227 | "dist": "", 228 | "distpn": "", 229 | "internal_id": "", 230 | "mfg": "", 231 | "mpn": "" 232 | }, 233 | "layer_pairs": [], 234 | "layer_presets": [], 235 | "viewports": [] 236 | }, 237 | "boards": [], 238 | "cvpcb": { 239 | "equivalence_files": [] 240 | }, 241 | "erc": { 242 | "erc_exclusions": [], 243 | "meta": { 244 | "version": 0 245 | }, 246 | "pin_map": [ 247 | [ 248 | 0, 249 | 0, 250 | 0, 251 | 0, 252 | 0, 253 | 0, 254 | 1, 255 | 0, 256 | 0, 257 | 0, 258 | 0, 259 | 2 260 | ], 261 | [ 262 | 0, 263 | 2, 264 | 0, 265 | 1, 266 | 0, 267 | 0, 268 | 1, 269 | 0, 270 | 2, 271 | 2, 272 | 2, 273 | 2 274 | ], 275 | [ 276 | 0, 277 | 0, 278 | 0, 279 | 0, 280 | 0, 281 | 0, 282 | 1, 283 | 0, 284 | 1, 285 | 0, 286 | 1, 287 | 2 288 | ], 289 | [ 290 | 0, 291 | 1, 292 | 0, 293 | 0, 294 | 0, 295 | 0, 296 | 1, 297 | 1, 298 | 2, 299 | 1, 300 | 1, 301 | 2 302 | ], 303 | [ 304 | 0, 305 | 0, 306 | 0, 307 | 0, 308 | 0, 309 | 0, 310 | 1, 311 | 0, 312 | 0, 313 | 0, 314 | 0, 315 | 2 316 | ], 317 | [ 318 | 0, 319 | 0, 320 | 0, 321 | 0, 322 | 0, 323 | 0, 324 | 0, 325 | 0, 326 | 0, 327 | 0, 328 | 0, 329 | 2 330 | ], 331 | [ 332 | 1, 333 | 1, 334 | 1, 335 | 1, 336 | 1, 337 | 0, 338 | 1, 339 | 1, 340 | 1, 341 | 1, 342 | 1, 343 | 2 344 | ], 345 | [ 346 | 0, 347 | 0, 348 | 0, 349 | 1, 350 | 0, 351 | 0, 352 | 1, 353 | 0, 354 | 0, 355 | 0, 356 | 0, 357 | 2 358 | ], 359 | [ 360 | 0, 361 | 2, 362 | 1, 363 | 2, 364 | 0, 365 | 0, 366 | 1, 367 | 0, 368 | 2, 369 | 2, 370 | 2, 371 | 2 372 | ], 373 | [ 374 | 0, 375 | 2, 376 | 0, 377 | 1, 378 | 0, 379 | 0, 380 | 1, 381 | 0, 382 | 2, 383 | 0, 384 | 0, 385 | 2 386 | ], 387 | [ 388 | 0, 389 | 2, 390 | 1, 391 | 1, 392 | 0, 393 | 0, 394 | 1, 395 | 0, 396 | 2, 397 | 0, 398 | 0, 399 | 2 400 | ], 401 | [ 402 | 2, 403 | 2, 404 | 2, 405 | 2, 406 | 2, 407 | 2, 408 | 2, 409 | 2, 410 | 2, 411 | 2, 412 | 2, 413 | 2 414 | ] 415 | ], 416 | "rule_severities": { 417 | "bus_definition_conflict": "error", 418 | "bus_entry_needed": "error", 419 | "bus_to_bus_conflict": "error", 420 | "bus_to_net_conflict": "error", 421 | "conflicting_netclasses": "error", 422 | "different_unit_footprint": "error", 423 | "different_unit_net": "error", 424 | "duplicate_reference": "error", 425 | "duplicate_sheet_names": "error", 426 | "endpoint_off_grid": "warning", 427 | "extra_units": "error", 428 | "footprint_filter": "ignore", 429 | "footprint_link_issues": "warning", 430 | "four_way_junction": "ignore", 431 | "global_label_dangling": "warning", 432 | "hier_label_mismatch": "error", 433 | "label_dangling": "error", 434 | "label_multiple_wires": "warning", 435 | "lib_symbol_issues": "warning", 436 | "lib_symbol_mismatch": "warning", 437 | "missing_bidi_pin": "warning", 438 | "missing_input_pin": "warning", 439 | "missing_power_pin": "error", 440 | "missing_unit": "warning", 441 | "multiple_net_names": "warning", 442 | "net_not_bus_member": "warning", 443 | "no_connect_connected": "warning", 444 | "no_connect_dangling": "warning", 445 | "pin_not_connected": "error", 446 | "pin_not_driven": "error", 447 | "pin_to_pin": "error", 448 | "power_pin_not_driven": "error", 449 | "same_local_global_label": "warning", 450 | "similar_label_and_power": "warning", 451 | "similar_labels": "warning", 452 | "similar_power": "warning", 453 | "simulation_model_issue": "ignore", 454 | "single_global_label": "ignore", 455 | "unannotated": "error", 456 | "unconnected_wire_endpoint": "warning", 457 | "unit_value_mismatch": "error", 458 | "unresolved_variable": "error", 459 | "wire_dangling": "error" 460 | } 461 | }, 462 | "libraries": { 463 | "pinned_footprint_libs": [], 464 | "pinned_symbol_libs": [] 465 | }, 466 | "meta": { 467 | "filename": "main.kicad_pro", 468 | "version": 3 469 | }, 470 | "net_settings": { 471 | "classes": [ 472 | { 473 | "bus_width": 12, 474 | "clearance": 0.2, 475 | "diff_pair_gap": 0.25, 476 | "diff_pair_via_gap": 0.25, 477 | "diff_pair_width": 0.2, 478 | "line_style": 0, 479 | "microvia_diameter": 0.3, 480 | "microvia_drill": 0.1, 481 | "name": "Default", 482 | "pcb_color": "rgba(0, 0, 0, 0.000)", 483 | "priority": 2147483647, 484 | "schematic_color": "rgba(0, 0, 0, 0.000)", 485 | "track_width": 0.2, 486 | "via_diameter": 0.6, 487 | "via_drill": 0.3, 488 | "wire_width": 6 489 | } 490 | ], 491 | "meta": { 492 | "version": 4 493 | }, 494 | "net_colors": null, 495 | "netclass_assignments": null, 496 | "netclass_patterns": [] 497 | }, 498 | "pcbnew": { 499 | "last_paths": { 500 | "gencad": "", 501 | "idf": "", 502 | "netlist": "", 503 | "plot": "output/", 504 | "pos_files": "", 505 | "specctra_dsn": "", 506 | "step": "", 507 | "svg": "", 508 | "vrml": "" 509 | }, 510 | "page_layout_descr_file": "" 511 | }, 512 | "schematic": { 513 | "annotate_start_num": 0, 514 | "bom_export_filename": "", 515 | "bom_fmt_presets": [], 516 | "bom_fmt_settings": { 517 | "field_delimiter": ",", 518 | "keep_line_breaks": false, 519 | "keep_tabs": false, 520 | "name": "CSV", 521 | "ref_delimiter": ",", 522 | "ref_range_delimiter": "", 523 | "string_delimiter": "\"" 524 | }, 525 | "bom_presets": [], 526 | "bom_settings": { 527 | "exclude_dnp": false, 528 | "fields_ordered": [ 529 | { 530 | "group_by": false, 531 | "label": "Reference", 532 | "name": "Reference", 533 | "show": true 534 | }, 535 | { 536 | "group_by": true, 537 | "label": "Value", 538 | "name": "Value", 539 | "show": true 540 | }, 541 | { 542 | "group_by": false, 543 | "label": "Datasheet", 544 | "name": "Datasheet", 545 | "show": true 546 | }, 547 | { 548 | "group_by": false, 549 | "label": "Footprint", 550 | "name": "Footprint", 551 | "show": true 552 | }, 553 | { 554 | "group_by": false, 555 | "label": "Qty", 556 | "name": "${QUANTITY}", 557 | "show": true 558 | }, 559 | { 560 | "group_by": true, 561 | "label": "DNP", 562 | "name": "${DNP}", 563 | "show": true 564 | }, 565 | { 566 | "group_by": false, 567 | "label": "#", 568 | "name": "${ITEM_NUMBER}", 569 | "show": false 570 | }, 571 | { 572 | "group_by": false, 573 | "label": "Description", 574 | "name": "Description", 575 | "show": false 576 | } 577 | ], 578 | "filter_string": "", 579 | "group_symbols": true, 580 | "include_excluded_from_bom": false, 581 | "name": "", 582 | "sort_asc": true, 583 | "sort_field": "Reference" 584 | }, 585 | "connection_grid_size": 50.0, 586 | "drawing": { 587 | "dashed_lines_dash_length_ratio": 12.0, 588 | "dashed_lines_gap_length_ratio": 3.0, 589 | "default_line_thickness": 6.0, 590 | "default_text_size": 50.0, 591 | "field_names": [], 592 | "intersheets_ref_own_page": false, 593 | "intersheets_ref_prefix": "", 594 | "intersheets_ref_short": false, 595 | "intersheets_ref_show": false, 596 | "intersheets_ref_suffix": "", 597 | "junction_size_choice": 3, 598 | "label_size_ratio": 0.375, 599 | "operating_point_overlay_i_precision": 3, 600 | "operating_point_overlay_i_range": "~A", 601 | "operating_point_overlay_v_precision": 3, 602 | "operating_point_overlay_v_range": "~V", 603 | "overbar_offset_ratio": 1.23, 604 | "pin_symbol_size": 25.0, 605 | "text_offset_ratio": 0.15 606 | }, 607 | "legacy_lib_dir": "", 608 | "legacy_lib_list": [], 609 | "meta": { 610 | "version": 1 611 | }, 612 | "net_format_name": "", 613 | "page_layout_descr_file": "", 614 | "plot_directory": "", 615 | "space_save_all_events": true, 616 | "spice_current_sheet_as_root": false, 617 | "spice_external_command": "spice \"%I\"", 618 | "spice_model_current_sheet_as_root": true, 619 | "spice_save_all_currents": false, 620 | "spice_save_all_dissipations": false, 621 | "spice_save_all_voltages": false, 622 | "subpart_first_id": 65, 623 | "subpart_id_separator": 0 624 | }, 625 | "sheets": [ 626 | [ 627 | "8c2a105f-a962-4917-8f56-dd50da121d32", 628 | "Root" 629 | ] 630 | ], 631 | "text_variables": {} 632 | } 633 | -------------------------------------------------------------------------------- /hardware/enclosure-text/output/millproject: -------------------------------------------------------------------------------- 1 | # general 2 | 3 | metric = true 4 | metricoutput = true 5 | nog64 = true 6 | nom6 = true 7 | zchange = 2.0mm 8 | zsafe = 2.0mm 9 | 10 | front = main-User_9.gbr 11 | front-output = main-User_9.gbr.ngc 12 | 13 | # milling 14 | 15 | mill-diameters = 0.15mm 16 | mill-feed = 75mm/min 17 | mill-infeed = 0.1mm 18 | mill-speed = 2000rpm 19 | zwork = -0.4mm 20 | invert-gerbers = true 21 | -------------------------------------------------------------------------------- /hardware/enclosure/_base.scad: -------------------------------------------------------------------------------- 1 | module rsquare(dim, r=0, center=false) { 2 | x = dim[0]; 3 | y = dim[1]; 4 | 5 | ox = center ? -x/2 : 0; 6 | oy = center ? -y/2 : 0; 7 | 8 | translate([ox, oy]) { 9 | union() { 10 | translate([r, 0]) 11 | square([x-2*r, y]); 12 | translate([0, r]) 13 | square([x, y-2*r]); 14 | translate([r, r]) 15 | circle(r); 16 | translate([x-r, r]) 17 | circle(r); 18 | translate([r, y-r]) 19 | circle(r); 20 | translate([x-r, y-r]) 21 | circle(r); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hardware/enclosure/bottom.scad: -------------------------------------------------------------------------------- 1 | use<_base.scad> 2 | 3 | $fn = 25; 4 | 5 | // board dimensions 6 | bx = 44.45; 7 | by = 34.93; 8 | 9 | // wall thickness 10 | wlr = 2.0; // left-right 11 | wtb = 1.0; // top-bottom 12 | wfr = 0.8; // front-rear 13 | 14 | // tolerance 15 | tol = 0.15; 16 | 17 | module Base() { 18 | translate([0, 0, -wfr]) 19 | linear_extrude(wfr) 20 | rsquare([bx+2*wlr+2*tol, by+2*wtb+2*tol], r=3, center=true); 21 | } 22 | 23 | module Snaps() { 24 | sx = 2; 25 | sy = 1; 26 | sz = 3.3; 27 | 28 | snaps = [ 29 | [[-11.4, by/2], 90], 30 | [[ 11.4, by/2], 90], 31 | [[ bx/2, 3.5], 0], 32 | [[ bx/2, -5.5], 0], 33 | [[ 11.4, -by/2], 270], 34 | [[-11.4, -by/2], 270], 35 | [[-bx/2, -5.5], 180], 36 | [[-bx/2, 3.5], 180], 37 | // these two are joined to form a single solid support 38 | [[ 0, 3.5], 0], 39 | [[ 0, 3.5], 180], 40 | ]; 41 | 42 | for (s = snaps) 43 | translate(s[0]) 44 | rotate([0, 0, s[1]]) 45 | // single snap 46 | translate([0, sx/2]) 47 | rotate([90, 0, 0]) 48 | linear_extrude(sx) 49 | polygon([[0, 0], [-sy, 0], [-sy, sz], [0, sz], [0.5, sz-0.5], [0, sz-1.0]]); 50 | } 51 | 52 | Base(); 53 | Snaps(); 54 | -------------------------------------------------------------------------------- /hardware/enclosure/slide.scad: -------------------------------------------------------------------------------- 1 | use<_base.scad> 2 | 3 | $fn = 25; 4 | 5 | // switch dimensions 6 | sx = 4.5; 7 | sy = 3; 8 | travel = 1.5; 9 | 10 | // wall thickness 11 | wlr = 2.0; // left-right 12 | 13 | module Top() { 14 | linear_extrude(wlr) 15 | rsquare([sx, sy], r=sy/2, center=true); 16 | } 17 | 18 | module Bottom() { 19 | translate([0, 0, -wlr/2]) 20 | linear_extrude(wlr/2+0.001) // prevent rendering issues 21 | difference() { 22 | square([sx+travel+1.5, sy+2], center=true); 23 | square([1.5, sy+2], center=true); 24 | } 25 | } 26 | 27 | Top(); 28 | Bottom(); 29 | -------------------------------------------------------------------------------- /hardware/enclosure/top.scad: -------------------------------------------------------------------------------- 1 | use<_base.scad> 2 | 3 | $fn = 25; 4 | 5 | // board dimensions 6 | bx = 44.45; 7 | by = 34.93; 8 | 9 | // wall thickness 10 | wlr = 2.0; // left-right 11 | wtb = 1.0; // top-bottom 12 | wfr = 0.8; // front-rear 13 | 14 | // tolerance 15 | tol = 0.15; 16 | 17 | // total height + 0.2mm for soldering inaccuracies: 18 | // - buttons: 1.8mm 19 | // - PCB: 0.8mm 20 | // - USB-C: 3.2mm 21 | z = 6; 22 | 23 | module Base() { 24 | translate([0, 0, -z]) 25 | linear_extrude(z+wfr) 26 | rsquare([bx+2*wlr+2*tol, by+2*wtb+2*tol], r=3, center=true); 27 | } 28 | 29 | module Extrude() { 30 | translate([0, 0, -z]) 31 | linear_extrude(z) 32 | rsquare([bx+2*tol, by+2*tol], r=1.5-tol, center=true); 33 | } 34 | 35 | module ExtrudeSlideSwitch() { 36 | // see CutoutSlideSwitch() for math info 37 | translate([-bx/2-0.5-tol, (12+10.5)/2, -z]) 38 | linear_extrude(z) 39 | square([1, 9+4*tol], center=true); 40 | } 41 | 42 | module CutoutDisplay() { 43 | translate([0, 12]) 44 | linear_extrude(wfr+0.001) // prevent rendering issues 45 | rsquare([25, 8], r=1, center=true); 46 | } 47 | 48 | module CutoutButtons() { 49 | buttons = [ 50 | [-2.0, 0.0], 51 | [-1.0, 0.0], 52 | [ 0.0, 0.0], 53 | [ 1.0, 0.0], 54 | [ 2.0, 0.0], 55 | [-2.0, -1.0], 56 | [-1.0, -1.0], 57 | [ 0.0, -1.0], 58 | [ 1.0, -1.0], 59 | [ 2.0, -1.0], 60 | [-1.5, -2.0], 61 | [-0.5, -2.0], 62 | [ 0.5, -2.0], 63 | [ 1.5, -2.0], 64 | ]; 65 | 66 | translate([0, 1.6]) 67 | linear_extrude(wfr+0.001) 68 | for (b = buttons) 69 | translate([b[0]*8.89, b[1]*7.62]) 70 | circle(2); 71 | } 72 | 73 | module CutoutSlideSwitch() { 74 | // top and bottom positions are is 12mm and 10.5mm away from the center 75 | // the main body of the switch is 4.5mm x 3mm, with 1.5mm travel 76 | translate([-bx/2-tol, (12+10.5)/2, -z/2]) 77 | rotate([-90, 0, 90]) 78 | linear_extrude(wlr) 79 | rsquare([4.5+1.5+2*tol, 3+2*tol], r=1.5, center=true); 80 | } 81 | 82 | module CutoutUSB() { 83 | w = 8.89; 84 | h = 3.2; 85 | 86 | translate([-w/2, by/2+tol-0.001, -z+h]) 87 | rotate([-90, 0, 0]) 88 | linear_extrude(wtb+0.001) 89 | square([w, h]); 90 | } 91 | 92 | module CutoutCharm() { 93 | // pads are 9mm and 11mm away from the center, but this would be too fragile 94 | // make holes at -8.5mm and -11.5mm instead 95 | translate([bx/2+tol, -8.5, -z+2]) 96 | rotate([0, 90, 0]) 97 | linear_extrude(wlr) { 98 | circle(0.75); 99 | translate([0, -3, 0]) circle(0.75); 100 | } 101 | } 102 | 103 | module Supports() { 104 | middle = 1.6-7.62/2; // Y dimension between buttons, see CutoutButtons() 105 | 106 | supports = [ 107 | [-bx/2+2-tol, by/2-0.5+tol], // top left 108 | [ bx/2-2+tol, by/2-0.5+tol], // top right 109 | [-bx/2+2-tol, -by/2+0.5-tol], // bottom left 110 | [ bx/2-2+tol, -by/2+0.5-tol], // bottom right 111 | [-bx/2+2-tol, middle], // middle left 112 | [ 0, middle], // middle center 113 | [ bx/2-2+tol, middle], // middle right 114 | ]; 115 | 116 | translate([0, 0, -1.9]) 117 | linear_extrude(1.9) 118 | for (s = supports) 119 | translate([s[0], s[1], -1.8]) 120 | square([4, 1], center=true); 121 | } 122 | 123 | module SnapHoles() { 124 | // almost the same as Snaps() in bottom.scad 125 | sx = 3; // wider than a snap 126 | sy = 1; 127 | sz = 3.3; 128 | 129 | snaps = [ 130 | [[-11.4, by/2], 90], 131 | [[ 11.4, by/2], 90], 132 | [[ bx/2, 3.5], 0], 133 | [[ bx/2, -5.5], 0], 134 | [[ 11.4, -by/2], 270], 135 | [[-11.4, -by/2], 270], 136 | [[-bx/2, -5.5], 180], 137 | [[-bx/2, 3.5], 180], 138 | ]; 139 | 140 | for (s = snaps) 141 | translate(s[0]) 142 | rotate([0, 0, s[1]]) 143 | // single snap hole 144 | translate([tol, sx/2, -z]) // extra tolerance to make hole deeper 145 | rotate([90, 0, 0]) 146 | linear_extrude(sx) 147 | polygon([[0, sz], [0.5, sz-0.5], [0, sz-1.0]]); 148 | } 149 | 150 | difference() { 151 | Base(); 152 | difference() { 153 | Extrude(); 154 | Supports(); 155 | } 156 | ExtrudeSlideSwitch(); 157 | CutoutDisplay(); 158 | CutoutButtons(); 159 | CutoutSlideSwitch(); 160 | CutoutUSB(); 161 | CutoutCharm(); 162 | SnapHoles(); 163 | } 164 | -------------------------------------------------------------------------------- /tools/convert.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if len(sys.argv) != 3: 4 | print("usage: source.pbm source.txt", file=sys.stderr) 5 | sys.exit(1) 6 | 7 | # get and process glyph data 8 | 9 | with open(sys.argv[1], "rt") as glyph_file: 10 | lines = glyph_file.readlines() 11 | 12 | assert lines[0].strip() == "P1" 13 | if lines[1].startswith("#"): 14 | lines.pop(1) 15 | 16 | t_width, t_height = lines[1].split(" ") 17 | t_width = int(t_width) 18 | t_height = int(t_height) 19 | 20 | data = [0] * t_width 21 | x = 0 22 | y = 0 23 | 24 | for line in lines[2:]: 25 | for value in line.strip(): 26 | if value == "1": 27 | data[x] |= (1 << y) 28 | x += 1 29 | if x == t_width: 30 | x = 0 31 | y += 1 32 | 33 | # get glyph widths 34 | 35 | with open(sys.argv[2], "rt") as metadata_file: 36 | g_widths = [int(w) for w in metadata_file.read().splitlines()] 37 | 38 | # build Go data 39 | 40 | print("package display") 41 | print() 42 | print("var font = [][]uint16{") 43 | 44 | d = 0 45 | g = 32 46 | for width in g_widths: 47 | print(f"/* {chr(g)} */ {{", end="") 48 | for n in data[d:d+width]: 49 | print(f"0x{n:04X}, ", end="") 50 | print(f"}},") 51 | d += width 52 | g += 1 53 | 54 | print("}") 55 | -------------------------------------------------------------------------------- /tools/icons.pbm: -------------------------------------------------------------------------------- 1 | P1 2 | # Created by GIMP version 3.0.2 PNM plug-in 3 | 24 8 4 | 0111110011011000001000001100010010011000011100001000010010000000111110 5 | 0011000100100010000010000001111100111110000010000000000000000000000000 6 | 0000000000000000000000000000000000000000000000000000 -------------------------------------------------------------------------------- /tools/icons.txt: -------------------------------------------------------------------------------- 1 | 8 2 | 8 3 | 8 -------------------------------------------------------------------------------- /tools/newstroke.pbm: -------------------------------------------------------------------------------- 1 | P1 2 | # Created by GIMP version 3.0.4 PNM plug-in 3 | 964 16 4 | 0000000100001001000000100010000000100000001100010000011100000001000000 5 | 1000100000000010000000000000000000000000000000000000000010000011100000 6 | 0001000000011110000011111110000000100000011111100000011100001111111000 7 | 0011100000001110000000000000000000000000000000000000000011100000000000 8 | 0000000000100000011111000000011110000111100000011111100011111100000111 9 | 1100010000010001000000010001000001000100000000100000100010000010000011 10 | 1000001111100000001110000001111100000001111000011111110001000001000100 11 | 0000010001000000000100010000010001000001000111111000111000100000000011 12 | 1000001000000000000000100000000000000100000000000000000000001000000000 13 | 0000001100000000000010000000010000100010000000100000000000000000000000 14 | 0000000000000000000000000000000000000000000000001000000000000000000000 15 | 0000000000000000000000000000000000000000000000110001000110000000000000 16 | 0000000000000000000000000000000000000000000000000000000000000000000000 17 | 0000000000000000000000000000000000000000000000000000000000000100001001 18 | 0000001000100000011110000100100100001000100000010000010000010000010010 19 | 0100000000000000000000000000000000000001000001000100000011000000100001 20 | 0000000000100000010000000100000000001000000000000010000100010000010001 21 | 0000000000000000000000000000000000000001000100000000110000000000100000 22 | 0100001000001000010001000100000100000000100000000010000000010000010001 23 | 0000000100010000100001000000001000001000100000100001000100001000010000 24 | 0100010000010000100000100001000000100000010000010001000000010001000000 25 | 0001000100000100010000010000000010001000000100000000001000010100000000 26 | 0000000100000000000001000000000000000000000010000000000000010000000000 27 | 0000100000000100001000100000001000000000000000000000000000000000000000 28 | 0000000000000000000000000000000010000000000000000000000000000000000000 29 | 0000000000000000000000000000010000010000010000000000000000000000000000 30 | 0000000000000000000000000000000000000000000000000000000000000000000000 31 | 0000000000000000000000000000000000000000000001000010010000001000100000 32 | 1010010001001010000010001000000100001000000010000011111000000000000000 33 | 0000000000000000000000010000100000100001010000000000001000000001000000 34 | 0100000010000000000100000000000000100010000010001000001000000000000000 35 | 0000000000000000000000000000100000110011000000010100000100000100001000 36 | 0000010000100001000000001000000000100000000100000100010000000100010001 37 | 0000010000000011000110001100001000100000100010000010001000001000010000 38 | 0100010000000000001000000100000100001000001000010000000001000010001000 39 | 0010001000000001000010000001000000000010001000100000000000000010000000 40 | 0000010000000000000000000000100000000000000100000000000000100000000000 41 | 0000001000000010000000000000000000000000000000000000000000000000000000 42 | 0000000000000000100000000000000000000000000000000000000000000000000000 43 | 0000000000000100000100000100000000000000101111101000000001100000000001 44 | 1111000000000001000000000000110000000000000001111000000000000000000001 45 | 1000000000111111000000000000010001001000001111111100010010000000110010 46 | 0000100010000010000010000000100000001000000000000000000000000000000000 47 | 0000001000001000001000100100000000000010000000100000001000000010000000 48 | 0001000000000000001000100000100010000010000000000000000000000000000000 49 | 0000000000001000010000001000000101000001000001000100000000010000100001 50 | 0000000010000000010000000001000001000100000001000100100000010000000011 51 | 0001100011000010001000001000100000100010000010000100000100010000000000 52 | 0010000001000001000010000010000010000000100000100010000010001000000001 53 | 0000100000001000000000100000000000000000000000000000000000010000000000 54 | 0000000000001000000000000001000000000000001000000000000000001000000010 55 | 0000000000000000000000000000000000000000000000000000000000000000000000 56 | 1000000000000000000000000000000000000000000000000000000000000000000100 57 | 0001000001000000000000000100000100000000101000000000010001000000000010 58 | 1000000000001010000000000000010010000000111111100000101000000000100001 59 | 0110000000000100010010000001000100000100100000000001000000100100000010 60 | 0001000000000100000101000000000000000000000000000000000000001000001000 61 | 0010000001000000000000100000010000000010000000101110000010000000000000 62 | 0100000100010000100000100000000000000001000000000000010000000000010000 63 | 0100111010000001010000010000010001000000000100000100010000000010000000 64 | 0100000000010000010001000000010001010000000100000000101010100010100010 65 | 0010000010001000001000100000100001000001000100000000000010000001000001 66 | 0000100000100000100010001000000101000000010100000000100000100000001000 67 | 0000001000000000000000000000000000011110000111100000001111000001111000 68 | 0111100001111100000111100011111000010000100010001000100000111101110000 69 | 1111100000011000001111000000011110001011000011110000111111000100001000 70 | 1000001000100000001000100000100010000100011111000001000001000001000000 71 | 0000000010100010100000010010000000000100010000000001000100000000001001 72 | 0000000000110100100000010000001000010011111100001000100001000000000100 73 | 0000000000010001000000101000000000010000000110000000000001000000000100 74 | 0001010000000010000000000000000000000000000100000010010010000001000000 75 | 0000001000001110000001000100001100010000101110000000000100000011100000 76 | 1000001000100001000000100000000000000010000000000100001001001001000010 77 | 0010000100001000010000000001000001000100000000100000000100000000010000 78 | 0100010000000100011000000001000000001010101000101000100010000010001000 79 | 0100001000001000010000100000100000000000100000010000010000010001000000 80 | 1000100010000001010000000101000000001000001000000001000000001000000000 81 | 0000000000000000000000010001000100000100000000100010001000010000010000 82 | 0010001000100001000100001000100100001000001000100010001000010000100100 83 | 0010001000001000100011000001000010000010000001000010001000001000100000 84 | 0010000100010000100001000000010000100000000000001000000000000010010100 85 | 1000001000111110000001000100000000100000100000111110001000000001010100 86 | 1000001001000100001000000001000010000100010000000001000000000000010001 87 | 0000000111000000001000000010100000000000010000000001000010001000000010 88 | 0000000000000000000000000001000000100100100000010000000000010000000001 89 | 0000010001000000000010001100010000000001000001000100000100011000100001 90 | 0000010000011111110000010000000010000010100010010000100010000111110000 91 | 0100000000010000010001111100001111000001000000000111111100010000000100 92 | 0101000000010000000010010010001001001000100000100011111000001000001000 93 | 0111110000000111000000001000000100000100000100010000001000100010000000 94 | 1000000000100000000100000010000000010000000010000000000000000000000000 95 | 0000000100010000100010000000010000100010000100000100000100001000100001 96 | 0001000010001010000010000010001000100010000100010000100010000100010000 97 | 1000100000010000000000100000010000100001000100001000100010000100010000 98 | 0100010000001000010000000000000001000011001000100010001000010000000010 99 | 0011110001111000010000000100001000000001000000100111001000010000101010 100 | 0001001111110000101000100010000000010000000000000100010000000010100000 101 | 0100000001000100000000000100000000010000000000000000100000000000000000 102 | 0000000000100000001000001000000100000000001000000000001000100001000000 103 | 0000100010000010000000100000100000100000111010000000000000100000000000 104 | 0000000010000001000000101000101000001000100001000010000100000000010000 105 | 0100010000000010000000010001110001000001000100000001000100100000010000 106 | 0000100100100010010010001000001000100000000010000010000100100000000000 107 | 1000000010000001000001000001000100000001010101000000010100000000100000 108 | 0001000000100000000010000000100000000000000000000000000001111100010000 109 | 1000100000000100001000100011000001000001000010001000010001000010001100 110 | 0000100000100010001000100001000100001000100001000100001000100000001000 111 | 0000001000000100001000010001000001001001000000101000000100010000001000 112 | 0010000000000000100001001100001001010010001000000000100010000000001000 113 | 1000000000100010000000001000010000000010001000000100100000101010100000 114 | 1101010000100000000000000000000001000100000000100100000100000001000100 115 | 1000000001000000000100000000000001111111000000001111111000000000100000 116 | 0010000010000001000000000100000000000010001111111000000000100010000010 117 | 0000001000001000001000000000100000000000010000000000000000000001000001 118 | 0000001001001010000010001000010000010001000000000100000100010000000010 119 | 0000000100000100010000010001000000010001000100000100000000100000100010 120 | 0010100010000010001000000000100000100001000100000000000100000010000001 121 | 0000010000001010000000010101010000000101000000001000000010000000100000 122 | 0000100000001000000000000000000000000000100001000100001000100000000100 123 | 0010001111000000010000010000100010000100010000100010100000100000100010 124 | 0010001000010001000010001000010001000010001000000001100000001000000100 125 | 0010000100010000010101010000000100000001000100000100000001000001000001 126 | 0000000000000010100010100001000000001000010000000100001111000111100010 127 | 0000000100001000000000100001000010101000011111100100000000100000100000 128 | 0000000000000001111111100000001001000010011000010000101000000000100000 129 | 0010000000000000000010000000000000000000000000010000000010000010000001 130 | 0000000010000000000000100000000100000000001000100000100000001000001000 131 | 0010000000010000000000000010000001111111000000100000000000000100111100 132 | 0000111110000100000100010000000001000010000100000000100000000100000100 133 | 0100000100010000000100010001000001000000001000001000100010100010000010 134 | 0010000000001000001000010001000000000001000000100000010000010000001010 135 | 0000000101010100000010001000000010000000100000001000000000010000001000 136 | 0000000000000000000000001000010001000010001000000001000010001000000000 137 | 0100000100001000100001000100001000100100001000001000100010001000010001 138 | 0000100010000100010000100010000000000100000010000001000010000010100000 139 | 0101010100000010100000001010000010000000010000010000010000000000000001 140 | 0000010000001000111110000010000010000000010001000000111110001000000100 141 | 0000001000001001000100000100000000100001000000010000000000000000000000 142 | 1000100000010010100000101001000100000100000000001000000010000000000000 143 | 0000100000000000000000000000000100000000100000100000010000000100000000 144 | 0000001000000001000000000010001000001000000100000010000010000000010000 145 | 0000000000010000000000000000010000000000000001000000000001000001000100 146 | 0001000010000000010000100001000000001000000000100001000100000100010000 147 | 0001000100001000010000000010000010001000011000100000100010000000001011 148 | 0010000100001000000000010000001000000100000100000010100000000101010100 149 | 0000100010000000100000010000000010000000000100000010000000000000000000 150 | 0000000010000100010000100010000000010000100010000000000100000010001000 151 | 1000010001000010001001000010000010001000100010000100010000100010000100 152 | 0100001000100000000000100000100000010000100000101000000101010100000100 153 | 0100000010100000100000000100000100000100000000000000101111101000000100 154 | 1000000000010001000000000100010000000000100100000000100111110000000100 155 | 0000100001111110010000010000000100000000010000000000001000100000001111 156 | 0000010010010000100001000000000001000001000000000000000000100000001000 157 | 0000000000100010000000000100010000000100000010000000001000010000000001 158 | 0000100001000001000100000001000000010001000000001000001000010000001000 159 | 0000000000001000000001000000001100011000010000010001000010000010000100 160 | 0100010000010000000010000000001000010001000001000100000010000100001000 161 | 0100000000100000100010000110000100010000100000000001001010000100001000 162 | 0100001000000010000000100010000000010000000000100010000001000001000000 163 | 1000000100000000100000000000100000100000000000000000000000000010000100 164 | 0100010000010000000010001000100000000001000000011110001000010001000010 165 | 0010001000100000100010001000100001000010010000100010000010001000100000 166 | 0100001000001000000100001000000100000000100010000001000100000001000001 167 | 0000000001000001000001000000000000000000000000000000101000000000001010 168 | 0000000001000100000000001010000000000101000000000000111111100000000010 169 | 1000000011000110000000000100000000000010001000000000100000010001100000 170 | 0111101000000000001000100000000000000000000000000010000000000000100010 171 | 0000000000111000001111111000111111100001111000000000010000011110000000 172 | 1110000000010000000011100000011100000010000100000001000000000000010000 173 | 0000010000000000111000000100000100011111000000011110000111100000011111 174 | 1000100000000001111000010000010001000111000001000001000111111000100000 175 | 1000100000100000111000001000000000001111000001000001000011110000000010 176 | 0000000111000000000100000000001000100000010000010000001000000111111000 177 | 1110000000001000111000000000000000000000000000011111000111100000001111 178 | 0000011110000111100000010000000000100010000100010000100010001000011000 179 | 1000100010001000010000011000001111000000011110001000000011110000000111 180 | 0000111110000001000000001000100000100000100000010000011111000000110001 181 | 0001100000000000000000000000000000000110000000000001000000000001111100 182 | 0000000011000000000000110000000000000000000000000000110000000000111000 183 | 0000000000000000000000000000000000000000000000000000000000000000000000 184 | 0000000000000000000000000000000000100000000000000000000000000000000000 185 | 0000000000000000000000000000000000000000000000000000000000000000000000 186 | 0000000000000000000000000001000000000000000000000000000000000000000000 187 | 0000000000000000000000000000000000000000000000000000000000000000000000 188 | 0000000000000000000000000000000000000000000000000000000000000000000000 189 | 0000000000000000000000011100000000000000000000000000000000000000000000 190 | 0000000000000000000000000000000000000000000000000000000000000000000000 191 | 0000000000000011111110000000000000000000000000000000000000000000000000 192 | 0000000000000000001000000000000000001000000000000000000000000000000000 193 | 0000000000000010000000000000100000000000000000000000000000000000000000 194 | 0000000000000000000000000000000100000000000000000000000000000000000000 195 | 0000000000000000000000000000000000000000000000000000000000000000000000 196 | 0000000000000000000000000000000000000000000000000000000000000000000000 197 | 0000000000000000000000000000000000000000000000000000000000000000000000 198 | 0000000000000000010000000000000000000000000000000000000000000000000000 199 | 0000000000000000000000000000000000000000000000000000000000000000000000 200 | 0000000000100000000000000000000000000000000000000000000000000000000000 201 | 0000000000000000000000000000000000000000000000000000000000000000000000 202 | 0000000000000000000000000000000000000000000000000000000000000000000000 203 | 0000000000000000000000000000000000000000000000000000000000000000000000 204 | 0000000000000000000000000000000000000000000000000000000000000000000000 205 | 0000000000000000000000000000000000000000000000000000000000000000000000 206 | 0100000000000000000010000000000000000000000000000000000000000000000010 207 | 0000000000001000000000000000000000000000000000000000000000000000000000 208 | 0000000000000001000000000000000000000000000000000000000000000000000000 209 | 0000000000000000000000000000000000000000000000000000000000000000000000 210 | 0000000000000000000000000000000000000000000000000000000000000000000000 211 | 0000000000000000000000000000000000000000000000000000000000000000000000 212 | 0100000000000000000000000000000000000000000000000000000000000000000000 213 | 0000000000000000000000000000000000000000000000000000000000000000100000 214 | 0000000000000000000000000000000000000000000000000000000000000000000000 215 | 0000000000000000000000000000000000000000000000000000000000000000000000 216 | 0000000000000000000000000000000000000000000000000000000000000000000000 217 | 0000000000000000000000000000000000000000000000000000000000000000000000 218 | 0000000000000000000000000000000000000000000000000000000000000000000000 219 | 0000000000000000000000000000000000000000000000000000111000000000000000 220 | 0011000000000000000000000000000000000000000000000000100000000000001000 221 | 0000000000000000000000000000000000000000000000000000000000000000000110 222 | 0000000000000000000000000000000000000000000000000000000000000000000000 223 | 0000000000000000000000000000000000000000000000000000000000000000000000 224 | 000000000000000000000000 -------------------------------------------------------------------------------- /tools/newstroke.txt: -------------------------------------------------------------------------------- 1 | 5 2 | 4 3 | 8 4 | 12 5 | 10 6 | 10 7 | 11 8 | 5 9 | 7 10 | 7 11 | 10 12 | 10 13 | 5 14 | 10 15 | 4 16 | 10 17 | 10 18 | 10 19 | 10 20 | 10 21 | 10 22 | 10 23 | 10 24 | 10 25 | 10 26 | 10 27 | 4 28 | 5 29 | 8 30 | 10 31 | 8 32 | 9 33 | 13 34 | 10 35 | 10 36 | 10 37 | 10 38 | 9 39 | 9 40 | 10 41 | 10 42 | 4 43 | 8 44 | 10 45 | 9 46 | 10 47 | 10 48 | 10 49 | 10 50 | 11 51 | 10 52 | 10 53 | 10 54 | 10 55 | 12 56 | 14 57 | 10 58 | 10 59 | 9 60 | 6 61 | 10 62 | 6 63 | 8 64 | 10 65 | 6 66 | 9 67 | 9 68 | 9 69 | 9 70 | 9 71 | 8 72 | 9 73 | 9 74 | 4 75 | 5 76 | 8 77 | 6 78 | 12 79 | 9 80 | 9 81 | 9 82 | 9 83 | 7 84 | 9 85 | 9 86 | 9 87 | 10 88 | 12 89 | 10 90 | 9 91 | 8 92 | 8 93 | 4 94 | 8 95 | 9 96 | 12 97 | 14 98 | 14 99 | 14 100 | 14 101 | 14 102 | 14 103 | 14 104 | 14 --------------------------------------------------------------------------------