Add mozc dial version.

Co-authored-by: Takashi Toyoshima <toyoshim@google.com>
Co-authored-by: Shun Ikejima <ikejima@google.com>
This commit is contained in:
Shun Ikejima
2025-10-01 10:01:00 +09:00
parent 9b4017fb9b
commit 6783bfea76
88 changed files with 233948 additions and 0 deletions

View File

@@ -223,6 +223,18 @@ Please refer to the README in each directory for more details.
</td> </td>
<td> <td>
![dial](./images/dial.webp)
[Gboard ダイヤルバージョン](./mozc-dial/README_ja.md)<br>
[Gboard double sided version](./mozc-dial)
まるくおさめました。
- [Promotional video](https://youtu.be/BgdWyD0cBx4)
- [Announce](https://blog.google/intl/ja-jp/products/android-chrome-play/gboard-2025)
- Release date: Oct 1, 2025
- Source: [/mozc-dial](./mozc-dial)
</td> </td>
</tr> </tr>
</table> </table>

BIN
images/dial.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

49
mozc-dial/README.md Normal file
View File

@@ -0,0 +1,49 @@
[Japanese version](./README_ja.md) is available.
# Gboard Dial Version
This directory contains the design data and firmware for the Gboard Dial Version, released on October 1, 2025.
The Gboard Dial Version is not an official Google product.
![Gboard dial version](./images/dial.webp)
**We've made things well-rounded.**
"Keyboards are boring if you can only type on them."
In response to feedback from our users,
we have created a keyboard that you can turn.
The Gboard Dial Version,
realizing text input by rotation.
## Let's build it
![Gboard DIY](./images/diy.webp)
This year again, we are releasing 3D printing model data, board data, and firmware.
[Click here for the build guide](./buildguide.md)
## There's also a non-rotating Gboard
For those who are accustomed to conventional typing input,
we also offer the standard version of Gboard.
It provides a comfortable input experience
in conjunction with various Google services
such as voice input and translation functions.
[Available on Google Play](https://play.google.com/store/apps/details?id=com.google.android.inputmethod.latin)<br>
[Available on the App Store](https://apps.apple.com/us/app/gboard-the-google-keyboard/id1091700242)
## [Other versions](https://github.com/google/mozc-devices)
## License
See [LICENSE](../LICENSE) file in this directory.

49
mozc-dial/README_ja.md Normal file
View File

@@ -0,0 +1,49 @@
Gboard[English version](./README.md) is available.
# Gboard ダイヤルバージョン
このディレクトリは 2025 年 10 月 1 日にリリースされた Gboard ダイヤルバージョンの設計データとファームウエアを含んでいます。
Gboard ダイヤルバージョンは正式な Google 製品ではありません。
![Gboard dial version](./images/dial.webp)
**まるくおさめました。**
「キーボードは打つだけじゃつまらない」
そんなユーザーのみなさまの声に応えて
まわす事ができるキーボードを作りました。
回転による文字入力を実現した、
Gboard ダイヤルバージョン
## 作ってみよう
![Gboard DIY](./images/diy.webp)
今年も 3D プリント用モデルデータ、基板データ、ファームウエアを公開しています。
[ビルドガイドはこちら](./buildguide_ja.md)
## まわらないGboardもあります
いままでの打つ文字入力に慣れ親しんだ方には、
通常版の Gboard もご用意しております。
音声入力や翻訳機能など
Google の各種サービスと連携した
快適な入力体験を提供します。
[Google Play で公開中](https://play.google.com/store/apps/details?id=com.google.android.inputmethod.latin&hl=ja)<br>
[App Store で公開中](https://apps.apple.com/jp/app/id1091700242)
## [その他のバージョン](https://github.com/google/mozc-devices)
## ライセンス
このディレクトリに置かれた [LICENSE](../LICENSE) ファイルを参照してください。

View File

@@ -0,0 +1,614 @@
(kicad_symbol_lib
(version 20241209)
(generator "kicad_symbol_editor")
(generator_version "9.0")
(symbol "RPR220"
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "U"
(at 11.938 0.254 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Value" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "RPR220_0_1"
(rectangle
(start -10.16 7.62)
(end 8.89 -7.62)
(stroke
(width 0)
(type default)
)
(fill
(type none)
)
)
)
(symbol "RPR220_1_1"
(pin power_in line
(at -5.08 10.16 270)
(length 2.54)
(name "LED+"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at -5.08 -10.16 90)
(length 2.54)
(name "LED-"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 3.81 10.16 270)
(length 2.54)
(name "S+"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "4"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin output line
(at 3.81 -10.16 90)
(length 2.54)
(name "S-"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
(symbol "TBD62083AFNG"
(exclude_from_sim no)
(in_bom yes)
(on_board yes)
(property "Reference" "U"
(at -7.62 11.43 0)
(effects
(font
(size 1.27 1.27)
)
(justify left)
)
)
(property "Value" "TBD62083AFNG"
(at 8.128 11.43 0)
(effects
(font
(size 1.27 1.27)
)
)
)
(property "Footprint" ""
(at 18.034 -14.224 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Datasheet" ""
(at 63.881 -16.51 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(property "Description" ""
(at 0 0 0)
(effects
(font
(size 1.27 1.27)
)
(hide yes)
)
)
(symbol "TBD62083AFNG_0_1"
(rectangle
(start -7.62 10.16)
(end 7.62 -12.7)
(stroke
(width 0.254)
(type default)
)
(fill
(type background)
)
)
(polyline
(pts
(xy -1.778 2.794) (xy -1.778 2.794)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 2.032) (xy -1.778 2.032)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 1.27) (xy -1.778 1.27)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 -1.27) (xy -1.778 -1.27)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 -2.032) (xy -1.778 -2.032)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy -1.778 -2.794) (xy -1.778 -2.794)
)
(stroke
(width 0.254)
(type default)
)
(fill
(type none)
)
)
(polyline
(pts
(xy 0 0) (xy 0 0)
)
(stroke
(width 0.508)
(type default)
)
(fill
(type none)
)
)
)
(symbol "TBD62083AFNG_1_1"
(pin input line
(at -10.16 7.62 0)
(length 2.54)
(name "I1"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "1"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -10.16 5.08 0)
(length 2.54)
(name "I2"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "2"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -10.16 2.54 0)
(length 2.54)
(name "I3"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "3"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -10.16 0 0)
(length 2.54)
(name "I4"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "4"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -10.16 -2.54 0)
(length 2.54)
(name "I5"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "5"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -10.16 -5.08 0)
(length 2.54)
(name "I6"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "6"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -10.16 -7.62 0)
(length 2.54)
(name "I7"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "7"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin input line
(at -10.16 -10.16 0)
(length 2.54)
(name "I8"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "8"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 0 12.7 270)
(length 2.54)
(name "COMMON"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "10"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin power_in line
(at 0 -15.24 90)
(length 2.54)
(name "GND"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "9"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 7.62 180)
(length 2.54)
(name "O1"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "18"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 5.08 180)
(length 2.54)
(name "O2"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "17"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 2.54 180)
(length 2.54)
(name "O3"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "16"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 0 180)
(length 2.54)
(name "O4"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "15"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 -2.54 180)
(length 2.54)
(name "O5"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "14"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 -5.08 180)
(length 2.54)
(name "O6"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "13"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 -7.62 180)
(length 2.54)
(name "O7"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "12"
(effects
(font
(size 1.27 1.27)
)
)
)
)
(pin open_collector line
(at 10.16 -10.16 180)
(length 2.54)
(name "O8"
(effects
(font
(size 1.27 1.27)
)
)
)
(number "11"
(effects
(font
(size 1.27 1.27)
)
)
)
)
)
(embedded_fonts no)
)
)

View File

@@ -0,0 +1,268 @@
(footprint "Registor"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(descr "Resistor, Axial_DIN0207 series, Axial, Horizontal, pin pitch=10.16mm, 0.25W = 1/4W, length*diameter=6.3*2.5mm^2, http://cdn-reichelt.de/documents/datenblatt/B400/1_4W%23YAG.pdf")
(tags "Resistor Axial_DIN0207 series Axial Horizontal pin pitch 10.16mm 0.25W = 1/4W length 6.3mm diameter 2.5mm")
(property "Reference" "REF**"
(at 5.08 -2.37 0)
(layer "F.SilkS")
(uuid "feaf2315-9459-4e5b-bd06-106bc303634a")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "Registor"
(at 5.08 2.37 0)
(layer "F.Fab")
(uuid "b7ee0cd7-bfee-496b-92c1-d88704fe67fa")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "1c130978-1a4e-4a76-ac71-bca152c08cd9")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "6e87b304-2cc2-4cf8-972f-917038fbf24c")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr through_hole)
(fp_line
(start 1.04 0)
(end 1.81 0)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "22a140a5-abc0-4c93-a115-3ded4440fab9")
)
(fp_line
(start 1.81 -1.25)
(end 1.81 1.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "5f6b1dc8-667a-415b-a1e3-c7e42aeef191")
)
(fp_line
(start 1.81 1.25)
(end 8.35 1.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "d7fe590d-6c4d-493b-a2fe-4f11da735aef")
)
(fp_line
(start 8.35 -1.25)
(end 1.81 -1.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "ff63fa8b-95d7-4d33-b2ef-fdba53745757")
)
(fp_line
(start 8.35 1.25)
(end 8.35 -1.25)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "aa490bd9-5851-4ee5-8ddc-66514acacc3e")
)
(fp_line
(start 9.12 0)
(end 8.35 0)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "13d2f1d2-bd5c-4419-a16e-200917a08a2f")
)
(fp_line
(start -1.05 -1.25)
(end -1.05 1.25)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "9efa66f6-cbbc-47c9-aff8-bb812a01fbaf")
)
(fp_line
(start -1.05 1.25)
(end 11.21 1.25)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "91e4595b-97e0-4642-9ec3-33eecbbbec9e")
)
(fp_line
(start 11.21 -1.25)
(end -1.05 -1.25)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "74f6930d-5423-44f6-90c0-85de9a2c0381")
)
(fp_line
(start 11.21 1.25)
(end 11.21 -1.25)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "67678070-4bbb-4b79-9e9a-cbd87db09e2a")
)
(fp_line
(start 0 0)
(end 1.93 0)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "70f478af-b1a4-4cd2-ab56-4a7807fbc2ac")
)
(fp_line
(start 1.93 -1.25)
(end 1.93 1.25)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "64b03f75-96ab-4e17-99c4-76ee83adb12b")
)
(fp_line
(start 1.93 1.25)
(end 8.23 1.25)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "52b3dde8-bebe-4bd4-a737-5be4181ab430")
)
(fp_line
(start 8.23 -1.25)
(end 1.93 -1.25)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "03c00c4b-3bab-4fc0-b5b8-9d2b97e6979d")
)
(fp_line
(start 8.23 1.25)
(end 8.23 -1.25)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "5ecab737-d9fd-4523-9c92-e78848a90588")
)
(fp_line
(start 10.16 0)
(end 8.23 0)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "9bc9b716-621b-4490-8cc7-7c24a198ea74")
)
(fp_text user "${REFERENCE}"
(at 5.08 0 0)
(layer "F.Fab")
(uuid "490a7353-000a-4ef9-ab08-ee8fdad234e7")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole rect
(at 0 0)
(size 1.6 1.6)
(drill 0.8)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "2f91f3f3-9c91-4e72-beff-15cd0f00e8e7")
)
(pad "1" smd rect
(at 2.8 0)
(size 4 1.6)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "d830989d-f6fc-4c46-ab22-b3417967c9ee")
)
(pad "2" smd rect
(at 7.4 0)
(size 4 1.6)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "a4592d9f-b478-4019-9620-870fd4755a3f")
)
(pad "2" thru_hole rect
(at 10.16 0)
(size 1.6 1.6)
(drill 0.8)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "8344306a-f2e9-488c-89f8-795d49418b7b")
)
(embedded_fonts no)
(model "${KICAD9_3DMODEL_DIR}/Resistor_THT.3dshapes/R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal.wrl"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
)

View File

@@ -0,0 +1,140 @@
(footprint "SW_SMD_PUSH"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(property "Reference" "U?"
(at 0 -2.3175 0)
(layer "F.SilkS")
(uuid "498293b9-18b0-48f1-9245-c00bb35e9e0d")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "SW_SMD_PUSH"
(at 0 2.3175 0)
(layer "F.Fab")
(uuid "63e0fadf-b32a-4b1f-8390-ffb2c88d66e6")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "f0acfeba-f7ad-47f1-ae11-960c57518f44")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "a48d286d-863f-497b-b6ba-22cdff2d5e85")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(fp_rect
(start -2 -1.5)
(end 2 1.5)
(stroke
(width 0.1)
(type default)
)
(fill no)
(layer "F.SilkS")
(uuid "ba767487-c00f-44bd-be00-a7f4c4f5e65e")
)
(fp_poly
(pts
(xy -2.5 -0.65) (xy -2.5 0.65) (xy -1.6 0.65) (xy -1.6 -0.65)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "802c66cb-95f7-4c5b-91d9-2022495fb282")
)
(fp_poly
(pts
(xy 2.5 -0.65) (xy 2.5 0.65) (xy 1.6 0.65) (xy 1.6 -0.65)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "08c33208-8b0b-47f6-9252-bc7b2c92ce34")
)
(fp_line
(start -1.95 -1.465)
(end 1.95 -1.465)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "319941eb-077e-4fa2-85de-9c4bce2b945d")
)
(fp_line
(start -1.95 1.465)
(end -1.95 -1.465)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "66b54f29-ed22-4202-9803-3248c7758446")
)
(fp_line
(start 1.95 -1.465)
(end 1.95 1.465)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "d8aeb101-4303-4299-8599-99c708635726")
)
(fp_line
(start 1.95 1.465)
(end -1.95 1.465)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "746336be-2d43-4bfa-aa91-83aec73e25bb")
)
(pad "1" smd rect
(at -2.185 0)
(size 1.23 1.36)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "cf1cc7dc-5096-49bb-92a4-5fb837efe176")
)
(pad "2" smd rect
(at 2.185 0)
(size 1.23 1.36)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "a4d3fb0c-4b25-49ee-86b7-b641df4c182f")
)
(embedded_fonts no)
)

View File

@@ -0,0 +1,933 @@
(footprint "USBC_24P"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(property "Reference" "U?"
(at -0.018 -3.792933 0)
(layer "F.SilkS")
(uuid "9c148a2f-d7ab-467f-821c-1e86c17bb8e4")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "USBC_24P"
(at -0.018 6.1145 0)
(layer "F.Fab")
(uuid "a5256c6b-95e6-4dac-af41-6794549ea55c")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "461caaa0-bfed-4cda-b4ad-32324ca44159")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(layer "F.Fab")
(hide yes)
(uuid "5fb4da0c-0aaa-4385-8edc-8c2db009b715")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(fp_line
(start -4.32 5.2875)
(end -4.32 3.9685)
(stroke
(width 0.254)
(type default)
)
(layer "F.SilkS")
(uuid "d958d410-791b-412c-af0a-5bdc0d4db76b")
)
(fp_line
(start -4.319 1.2065)
(end -4.318 -0.6615)
(stroke
(width 0.254)
(type default)
)
(layer "F.SilkS")
(uuid "f8876005-4126-4a3a-8051-6eda96ec222e")
)
(fp_line
(start -3.273 -2.54)
(end -3.006 -2.54)
(stroke
(width 0.254)
(type default)
)
(layer "F.SilkS")
(uuid "813f1810-1a22-43b0-8a48-d06b715a44f1")
)
(fp_line
(start 3.256 -2.54)
(end 3.2735 -2.54)
(stroke
(width 0.254)
(type default)
)
(layer "F.SilkS")
(uuid "2399ab9b-f494-4ea9-8d36-abffbbfcce06")
)
(fp_line
(start 4.319 1.2065)
(end 4.318 -0.6615)
(stroke
(width 0.254)
(type default)
)
(layer "F.SilkS")
(uuid "86e67bc9-e91d-4096-9ffd-b9bad43ca5e4")
)
(fp_line
(start 4.32 5.2875)
(end -4.32 5.2875)
(stroke
(width 0.254)
(type default)
)
(layer "F.SilkS")
(uuid "4f13bc6b-f071-44d3-8026-33bf3e93eb73")
)
(fp_line
(start 4.32 5.2875)
(end 4.32 3.9685)
(stroke
(width 0.254)
(type default)
)
(layer "F.SilkS")
(uuid "676a7fa3-2f03-47b9-98aa-0737e5fbdabe")
)
(fp_poly
(pts
(xy -3.025 -1.7625) (xy -2.725 -1.7625) (xy -2.725 -0.8125) (xy -3.025 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "57910b7e-e3a9-43e5-a7ec-5002a1374165")
)
(fp_poly
(pts
(xy -2.775 -3.0625) (xy -2.475 -3.0625) (xy -2.475 -2.1125) (xy -2.775 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "8e44cad2-5a0e-4113-bfa5-25f07daa2044")
)
(fp_poly
(pts
(xy -2.525 -1.7625) (xy -2.225 -1.7625) (xy -2.225 -0.8125) (xy -2.525 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "9fda821b-37da-4a36-90af-b8be5f12f377")
)
(fp_poly
(pts
(xy -2.275 -3.0625) (xy -1.975 -3.0625) (xy -1.975 -2.1125) (xy -2.275 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "712dc44f-5b9a-40f0-8f8c-36764fe0c41b")
)
(fp_poly
(pts
(xy -2.025 -1.7625) (xy -1.725 -1.7625) (xy -1.725 -0.8125) (xy -2.025 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "3cc9c8c3-d755-4810-adb9-a9e04a31605b")
)
(fp_poly
(pts
(xy -1.775 -3.0625) (xy -1.475 -3.0625) (xy -1.475 -2.1125) (xy -1.775 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "3cdf9f6c-baad-4537-b138-703124fdf5a8")
)
(fp_poly
(pts
(xy -1.525 -1.7625) (xy -1.225 -1.7625) (xy -1.225 -0.8125) (xy -1.525 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "208a38a3-bdac-4808-b07d-72864f11628c")
)
(fp_poly
(pts
(xy -1.275 -3.0625) (xy -0.975 -3.0625) (xy -0.975 -2.1125) (xy -1.275 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "578c6201-0ece-417c-925e-9b86ce99319f")
)
(fp_poly
(pts
(xy -1.025 -1.7625) (xy -0.725 -1.7625) (xy -0.725 -0.8125) (xy -1.025 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "0bcecb00-305b-491d-aa6a-72999a6cb24a")
)
(fp_poly
(pts
(xy -0.775 -3.0625) (xy -0.475 -3.0625) (xy -0.475 -2.1125) (xy -0.775 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "0643670f-612c-4fd7-a8af-b7965257b426")
)
(fp_poly
(pts
(xy -0.525 -1.7625) (xy -0.225 -1.7625) (xy -0.225 -0.8125) (xy -0.525 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "423e2b5f-fdc1-4ce8-a31e-3eceac7eb70b")
)
(fp_poly
(pts
(xy -0.275 -3.0625) (xy 0.025 -3.0625) (xy 0.025 -2.1125) (xy -0.275 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "396b72d2-822e-4f6c-b718-d5f57bb4647e")
)
(fp_poly
(pts
(xy -0.025 -1.7625) (xy 0.275 -1.7625) (xy 0.275 -0.8125) (xy -0.025 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "8e0559f3-9598-4069-9889-1d05424781f9")
)
(fp_poly
(pts
(xy 0.225 -3.0625) (xy 0.525 -3.0625) (xy 0.525 -2.1125) (xy 0.225 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "f2f85ed2-e660-4c2f-8562-1cfca1656979")
)
(fp_poly
(pts
(xy 0.475 -1.7625) (xy 0.775 -1.7625) (xy 0.775 -0.8125) (xy 0.475 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "49e8fd1a-3178-40a4-a198-3b3c3e0233d2")
)
(fp_poly
(pts
(xy 0.725 -3.0625) (xy 1.025 -3.0625) (xy 1.025 -2.1125) (xy 0.725 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "a218e363-99ad-4d58-9478-814c9cd61e62")
)
(fp_poly
(pts
(xy 0.975 -1.7625) (xy 1.275 -1.7625) (xy 1.275 -0.8125) (xy 0.975 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "54e58b3e-397b-405b-ab6d-555fb0f3189f")
)
(fp_poly
(pts
(xy 1.225 -3.0625) (xy 1.525 -3.0625) (xy 1.525 -2.1125) (xy 1.225 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "57e5a0d9-9d1b-4f5a-93e5-e8b797e3929d")
)
(fp_poly
(pts
(xy 1.475 -1.7625) (xy 1.775 -1.7625) (xy 1.775 -0.8125) (xy 1.475 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "55dc0f11-88e2-4e4d-9b0b-f85d6ea8f86c")
)
(fp_poly
(pts
(xy 1.725 -3.0625) (xy 2.025 -3.0625) (xy 2.025 -2.1125) (xy 1.725 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "525591a4-223d-4495-b269-eac4f454610e")
)
(fp_poly
(pts
(xy 1.975 -1.7625) (xy 2.275 -1.7625) (xy 2.275 -0.8125) (xy 1.975 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "46129872-2220-40a5-b094-8d1b9b8868f2")
)
(fp_poly
(pts
(xy 2.225 -3.0625) (xy 2.525 -3.0625) (xy 2.525 -2.1125) (xy 2.225 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "745fa5be-e6bb-4039-8bb2-808a190068ef")
)
(fp_poly
(pts
(xy 2.475 -1.7625) (xy 2.775 -1.7625) (xy 2.775 -0.8125) (xy 2.475 -0.8125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "2dd8d6f9-7c7f-4bae-95fe-7358ac7d22ef")
)
(fp_poly
(pts
(xy 2.725 -3.0625) (xy 3.025 -3.0625) (xy 3.025 -2.1125) (xy 2.725 -2.1125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "0f8d3d0d-d7a3-435f-99f4-785d62963e2f")
)
(fp_poly
(pts
(xy -4.3325 1.4375) (xy -4.3575 1.439) (xy -4.382 1.4415) (xy -4.407 1.445) (xy -4.4315 1.45) (xy -4.4555 1.456)
(xy -4.4795 1.4635) (xy -4.5025 1.472) (xy -4.5255 1.482) (xy -4.548 1.4925) (xy -4.57 1.5045) (xy -4.5915 1.5175)
(xy -4.612 1.5315) (xy -4.632 1.5465) (xy -4.651 1.5625) (xy -4.669 1.5795) (xy -4.6865 1.5975) (xy -4.703 1.616)
(xy -4.7185 1.6355) (xy -4.733 1.656) (xy -4.7465 1.677) (xy -4.759 1.6985) (xy -4.7705 1.7205) (xy -4.781 1.7435)
(xy -4.79 1.7665) (xy -4.798 1.79) (xy -4.8045 1.814) (xy -4.81 1.8385) (xy -4.8145 1.863) (xy -4.8175 1.8875)
(xy -4.8195 1.9125) (xy -4.82 1.9375) (xy -4.82 3.2375) (xy -4.8195 3.2625) (xy -4.8175 3.2875) (xy -4.8145 3.312)
(xy -4.81 3.3365) (xy -4.8045 3.361) (xy -4.798 3.385) (xy -4.79 3.4085) (xy -4.781 3.4315) (xy -4.7705 3.4545)
(xy -4.759 3.4765) (xy -4.7465 3.498) (xy -4.733 3.519) (xy -4.7185 3.5395) (xy -4.703 3.559) (xy -4.6865 3.5775)
(xy -4.669 3.5955) (xy -4.651 3.6125) (xy -4.632 3.6285) (xy -4.612 3.6435) (xy -4.5915 3.6575) (xy -4.57 3.6705)
(xy -4.548 3.6825) (xy -4.5255 3.6935) (xy -4.5025 3.703) (xy -4.4795 3.7115) (xy -4.4555 3.719)
(xy -4.4315 3.725) (xy -4.407 3.73) (xy -4.382 3.7335) (xy -4.3575 3.736) (xy -4.3325 3.7375) (xy -4.3075 3.7375)
(xy -4.2825 3.736) (xy -4.258 3.7335) (xy -4.233 3.73) (xy -4.209 3.725) (xy -4.1845 3.719) (xy -4.161 3.7115)
(xy -4.1375 3.703) (xy -4.1145 3.6935) (xy -4.092 3.6825) (xy -4.07 3.6705) (xy -4.049 3.6575) (xy -4.028 3.6435)
(xy -4.0085 3.6285) (xy -3.989 3.6125) (xy -3.971 3.5955) (xy -3.9535 3.5775) (xy -3.937 3.559) (xy -3.9215 3.5395)
(xy -3.907 3.519) (xy -3.8935 3.498) (xy -3.881 3.4765) (xy -3.8695 3.4545) (xy -3.8595 3.4315) (xy -3.85 3.4085)
(xy -3.8425 3.385) (xy -3.8355 3.361) (xy -3.83 3.3365) (xy -3.8255 3.312) (xy -3.8225 3.2875) (xy -3.8205 3.2625)
(xy -3.82 3.2375) (xy -3.82 1.9375) (xy -3.8205 1.9125) (xy -3.8225 1.8875) (xy -3.8255 1.863) (xy -3.83 1.8385)
(xy -3.8355 1.814) (xy -3.8425 1.79) (xy -3.85 1.7665) (xy -3.8595 1.7435) (xy -3.8695 1.7205) (xy -3.881 1.6985)
(xy -3.8935 1.677) (xy -3.907 1.656) (xy -3.9215 1.6355) (xy -3.937 1.616) (xy -3.9535 1.5975) (xy -3.971 1.5795)
(xy -3.989 1.5625) (xy -4.0085 1.5465) (xy -4.028 1.5315) (xy -4.049 1.5175) (xy -4.07 1.5045) (xy -4.092 1.4925)
(xy -4.1145 1.482) (xy -4.1375 1.472) (xy -4.161 1.4635) (xy -4.1845 1.456) (xy -4.209 1.45) (xy -4.233 1.445)
(xy -4.258 1.4415) (xy -4.2825 1.439) (xy -4.3075 1.4375)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "fd84b075-9032-456a-94ae-d0c64049447d")
)
(fp_poly
(pts
(xy 4.3575 1.439) (xy 4.3325 1.4375) (xy 4.3075 1.4375) (xy 4.2825 1.439) (xy 4.258 1.4415) (xy 4.233 1.445)
(xy 4.209 1.45) (xy 4.1845 1.456) (xy 4.161 1.4635) (xy 4.1375 1.472) (xy 4.1145 1.482) (xy 4.092 1.4925)
(xy 4.07 1.5045) (xy 4.049 1.5175) (xy 4.028 1.5315) (xy 4.0085 1.5465) (xy 3.989 1.5625) (xy 3.971 1.5795)
(xy 3.9535 1.5975) (xy 3.937 1.616) (xy 3.9215 1.6355) (xy 3.907 1.656) (xy 3.8935 1.677) (xy 3.881 1.6985)
(xy 3.8695 1.7205) (xy 3.8595 1.7435) (xy 3.85 1.7665) (xy 3.8425 1.79) (xy 3.8355 1.814) (xy 3.83 1.8385)
(xy 3.8255 1.863) (xy 3.8225 1.8875) (xy 3.8205 1.9125) (xy 3.82 1.9375) (xy 3.82 3.2375) (xy 3.8205 3.2625)
(xy 3.8225 3.2875) (xy 3.8255 3.312) (xy 3.83 3.3365) (xy 3.8355 3.361) (xy 3.8425 3.385) (xy 3.85 3.4085)
(xy 3.8595 3.4315) (xy 3.8695 3.4545) (xy 3.881 3.4765) (xy 3.8935 3.498) (xy 3.907 3.519) (xy 3.9215 3.5395)
(xy 3.937 3.559) (xy 3.9535 3.5775) (xy 3.971 3.5955) (xy 3.989 3.6125) (xy 4.0085 3.6285) (xy 4.028 3.6435)
(xy 4.049 3.6575) (xy 4.07 3.6705) (xy 4.092 3.6825) (xy 4.1145 3.6935) (xy 4.1375 3.703) (xy 4.161 3.7115)
(xy 4.1845 3.719) (xy 4.209 3.725) (xy 4.233 3.73) (xy 4.258 3.7335) (xy 4.2825 3.736) (xy 4.3075 3.7375)
(xy 4.3325 3.7375) (xy 4.3575 3.736) (xy 4.382 3.7335) (xy 4.407 3.73) (xy 4.4315 3.725) (xy 4.4555 3.719)
(xy 4.4795 3.7115) (xy 4.5025 3.703) (xy 4.5255 3.6935) (xy 4.548 3.6825) (xy 4.57 3.6705) (xy 4.5915 3.6575)
(xy 4.612 3.6435) (xy 4.632 3.6285) (xy 4.651 3.6125) (xy 4.669 3.5955) (xy 4.6865 3.5775) (xy 4.703 3.559)
(xy 4.7185 3.5395) (xy 4.733 3.519) (xy 4.7465 3.498) (xy 4.759 3.4765) (xy 4.7705 3.4545) (xy 4.781 3.4315)
(xy 4.79 3.4085) (xy 4.798 3.385) (xy 4.8045 3.361) (xy 4.81 3.3365) (xy 4.8145 3.312) (xy 4.8175 3.2875)
(xy 4.8195 3.2625) (xy 4.82 3.2375) (xy 4.82 1.9375) (xy 4.8195 1.9125) (xy 4.8175 1.8875) (xy 4.8145 1.863)
(xy 4.81 1.8385) (xy 4.8045 1.814) (xy 4.798 1.79) (xy 4.79 1.7665) (xy 4.781 1.7435) (xy 4.7705 1.7205)
(xy 4.759 1.6985) (xy 4.7465 1.677) (xy 4.733 1.656) (xy 4.7185 1.6355) (xy 4.703 1.616) (xy 4.6865 1.5975)
(xy 4.669 1.5795) (xy 4.651 1.5625) (xy 4.632 1.5465) (xy 4.612 1.5315) (xy 4.5915 1.5175) (xy 4.57 1.5045)
(xy 4.548 1.4925) (xy 4.5255 1.482) (xy 4.5025 1.472) (xy 4.4795 1.4635) (xy 4.4555 1.456) (xy 4.4315 1.45)
(xy 4.407 1.445) (xy 4.382 1.4415)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "80c36ae4-d7ab-4b88-a422-ee9addc4c8de")
)
(fp_poly
(pts
(xy 4.3555 -3.0055) (xy 4.3305 -3.007) (xy 4.3055 -3.007) (xy 4.2805 -3.0055) (xy 4.256 -3.003) (xy 4.231 -2.9995)
(xy 4.2065 -2.9945) (xy 4.1825 -2.9885) (xy 4.159 -2.981) (xy 4.1355 -2.9725) (xy 4.1125 -2.963)
(xy 4.09 -2.952) (xy 4.068 -2.94) (xy 4.0465 -2.927) (xy 4.026 -2.913) (xy 4.0065 -2.898) (xy 3.987 -2.882)
(xy 3.969 -2.865) (xy 3.9515 -2.847) (xy 3.935 -2.8285) (xy 3.9195 -2.809) (xy 3.905 -2.7885) (xy 3.8915 -2.7675)
(xy 3.879 -2.746) (xy 3.8675 -2.724) (xy 3.8575 -2.701) (xy 3.848 -2.678) (xy 3.84 -2.6545) (xy 3.8335 -2.6305)
(xy 3.828 -2.606) (xy 3.8235 -2.5815) (xy 3.8205 -2.557) (xy 3.8185 -2.532) (xy 3.818 -2.507) (xy 3.818 -1.968)
(xy 3.696 -1.968) (xy 3.6715 -1.9675) (xy 3.6465 -1.9655) (xy 3.6215 -1.9625) (xy 3.597 -1.958) (xy 3.573 -1.9525)
(xy 3.549 -1.946) (xy 3.525 -1.938) (xy 3.502 -1.929) (xy 3.4795 -1.9185) (xy 3.457 -1.907) (xy 3.4355 -1.895)
(xy 3.4145 -1.8815) (xy 3.3945 -1.8665) (xy 3.375 -1.851) (xy 3.356 -1.8345) (xy 3.3385 -1.817) (xy 3.3215 -1.799)
(xy 3.3055 -1.78) (xy 3.2905 -1.76) (xy 3.276 -1.7395) (xy 3.263 -1.718) (xy 3.2515 -1.696) (xy 3.2405 -1.674)
(xy 3.231 -1.651) (xy 3.2225 -1.6275) (xy 3.215 -1.6035) (xy 3.209 -1.5795) (xy 3.204 -1.555) (xy 3.2 -1.5305)
(xy 3.1975 -1.5055) (xy 3.1965 -1.4805) (xy 3.1965 -1.4555) (xy 3.1975 -1.431) (xy 3.2 -1.406) (xy 3.204 -1.3815)
(xy 3.209 -1.357) (xy 3.215 -1.3325) (xy 3.2225 -1.309) (xy 3.231 -1.2855) (xy 3.2405 -1.2625) (xy 3.2515 -1.24)
(xy 3.263 -1.218) (xy 3.276 -1.197) (xy 3.2905 -1.176) (xy 3.3055 -1.1565) (xy 3.3215 -1.1375) (xy 3.3385 -1.119)
(xy 3.356 -1.1015) (xy 3.375 -1.085) (xy 3.3945 -1.0695) (xy 3.4145 -1.055) (xy 3.4355 -1.0415) (xy 3.457 -1.029)
(xy 3.4795 -1.0175) (xy 3.502 -1.0075) (xy 3.525 -0.9985) (xy 3.549 -0.9905) (xy 3.573 -0.9835) (xy 3.597 -0.978)
(xy 3.6215 -0.9735) (xy 3.6465 -0.9705) (xy 3.6715 -0.9685) (xy 3.696 -0.968) (xy 4.396 -0.968) (xy 4.421 -0.9685)
(xy 4.446 -0.9705) (xy 4.471 -0.9735) (xy 4.4955 -0.978) (xy 4.5195 -0.9835) (xy 4.5435 -0.9905)
(xy 4.5675 -0.9985) (xy 4.5905 -1.0075) (xy 4.613 -1.0175) (xy 4.6355 -1.029) (xy 4.657 -1.0415)
(xy 4.678 -1.055) (xy 4.698 -1.0695) (xy 4.7175 -1.085) (xy 4.7365 -1.1015) (xy 4.754 -1.119) (xy 4.771 -1.1375)
(xy 4.787 -1.1565) (xy 4.802 -1.176) (xy 4.816 -1.197) (xy 4.8295 -1.218) (xy 4.841 -1.24) (xy 4.852 -1.2625)
(xy 4.8615 -1.2855) (xy 4.87 -1.309) (xy 4.8775 -1.3325) (xy 4.8835 -1.357) (xy 4.8885 -1.3815) (xy 4.8925 -1.406)
(xy 4.895 -1.431) (xy 4.896 -1.4555) (xy 4.896 -1.4805) (xy 4.895 -1.5055) (xy 4.8925 -1.5305) (xy 4.8885 -1.555)
(xy 4.8835 -1.5795) (xy 4.8775 -1.6035) (xy 4.87 -1.6275) (xy 4.8615 -1.651) (xy 4.852 -1.674) (xy 4.841 -1.696)
(xy 4.8295 -1.718) (xy 4.818 -1.7365) (xy 4.818 -2.507) (xy 4.8175 -2.532) (xy 4.8155 -2.557) (xy 4.8125 -2.5815)
(xy 4.808 -2.606) (xy 4.8025 -2.6305) (xy 4.796 -2.6545) (xy 4.788 -2.678) (xy 4.7785 -2.701) (xy 4.7685 -2.724)
(xy 4.757 -2.746) (xy 4.7445 -2.7675) (xy 4.731 -2.7885) (xy 4.7165 -2.809) (xy 4.701 -2.8285) (xy 4.6845 -2.847)
(xy 4.667 -2.865) (xy 4.649 -2.882) (xy 4.6295 -2.898) (xy 4.61 -2.913) (xy 4.5895 -2.927) (xy 4.568 -2.94)
(xy 4.546 -2.952) (xy 4.5235 -2.963) (xy 4.5005 -2.9725) (xy 4.477 -2.981) (xy 4.4535 -2.9885) (xy 4.4295 -2.9945)
(xy 4.405 -2.9995) (xy 4.38 -3.003)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "679d7987-632f-4591-a7b8-2d8a793c7c39")
)
(fp_poly
(pts
(xy -4.2805 -3.0055) (xy -4.3055 -3.007) (xy -4.3305 -3.007) (xy -4.3555 -3.0055) (xy -4.38 -3.003)
(xy -4.405 -2.9995) (xy -4.4295 -2.9945) (xy -4.4535 -2.9885) (xy -4.477 -2.981) (xy -4.5005 -2.9725)
(xy -4.5235 -2.963) (xy -4.546 -2.952) (xy -4.568 -2.94) (xy -4.5895 -2.927) (xy -4.61 -2.913) (xy -4.6295 -2.898)
(xy -4.649 -2.882) (xy -4.667 -2.865) (xy -4.6845 -2.847) (xy -4.701 -2.8285) (xy -4.7165 -2.809)
(xy -4.731 -2.7885) (xy -4.7445 -2.7675) (xy -4.757 -2.746) (xy -4.7685 -2.724) (xy -4.7785 -2.701)
(xy -4.788 -2.678) (xy -4.796 -2.6545) (xy -4.8025 -2.6305) (xy -4.808 -2.606) (xy -4.8125 -2.5815)
(xy -4.8155 -2.557) (xy -4.8175 -2.532) (xy -4.818 -2.507) (xy -4.818 -1.7855) (xy -4.8225 -1.78)
(xy -4.8375 -1.76) (xy -4.852 -1.7395) (xy -4.865 -1.718) (xy -4.8765 -1.696) (xy -4.8875 -1.674)
(xy -4.897 -1.651) (xy -4.9055 -1.6275) (xy -4.913 -1.6035) (xy -4.919 -1.5795) (xy -4.924 -1.555)
(xy -4.928 -1.5305) (xy -4.9305 -1.5055) (xy -4.9315 -1.4805) (xy -4.9315 -1.4555) (xy -4.9305 -1.431)
(xy -4.928 -1.406) (xy -4.924 -1.3815) (xy -4.919 -1.357) (xy -4.913 -1.3325) (xy -4.9055 -1.309)
(xy -4.897 -1.2855) (xy -4.8875 -1.2625) (xy -4.8765 -1.24) (xy -4.865 -1.218) (xy -4.852 -1.197)
(xy -4.8375 -1.176) (xy -4.8225 -1.1565) (xy -4.8065 -1.1375) (xy -4.7895 -1.119) (xy -4.772 -1.1015)
(xy -4.753 -1.085) (xy -4.7335 -1.0695) (xy -4.7135 -1.055) (xy -4.6925 -1.0415) (xy -4.671 -1.029)
(xy -4.6485 -1.0175) (xy -4.626 -1.0075) (xy -4.603 -0.9985) (xy -4.579 -0.9905) (xy -4.555 -0.9835)
(xy -4.531 -0.978) (xy -4.5065 -0.9735) (xy -4.4815 -0.9705) (xy -4.4565 -0.9685) (xy -4.432 -0.968)
(xy -3.732 -0.968) (xy -3.707 -0.9685) (xy -3.682 -0.9705) (xy -3.657 -0.9735) (xy -3.6325 -0.978)
(xy -3.6085 -0.9835) (xy -3.5845 -0.9905) (xy -3.5605 -0.9985) (xy -3.5375 -1.0075) (xy -3.515 -1.0175)
(xy -3.4925 -1.029) (xy -3.471 -1.0415) (xy -3.45 -1.055) (xy -3.43 -1.0695) (xy -3.4105 -1.085)
(xy -3.3915 -1.1015) (xy -3.374 -1.119) (xy -3.357 -1.1375) (xy -3.341 -1.1565) (xy -3.326 -1.176)
(xy -3.312 -1.197) (xy -3.2985 -1.218) (xy -3.287 -1.24) (xy -3.276 -1.2625) (xy -3.2665 -1.2855)
(xy -3.258 -1.309) (xy -3.2505 -1.3325) (xy -3.2445 -1.357) (xy -3.2395 -1.3815) (xy -3.2355 -1.406)
(xy -3.233 -1.431) (xy -3.232 -1.4555) (xy -3.232 -1.4805) (xy -3.233 -1.5055) (xy -3.2355 -1.5305)
(xy -3.2395 -1.555) (xy -3.2445 -1.5795) (xy -3.2505 -1.6035) (xy -3.258 -1.6275) (xy -3.2665 -1.651)
(xy -3.276 -1.674) (xy -3.287 -1.696) (xy -3.2985 -1.718) (xy -3.312 -1.7395) (xy -3.326 -1.76) (xy -3.341 -1.78)
(xy -3.357 -1.799) (xy -3.374 -1.817) (xy -3.3915 -1.8345) (xy -3.4105 -1.851) (xy -3.43 -1.8665)
(xy -3.45 -1.8815) (xy -3.471 -1.895) (xy -3.4925 -1.907) (xy -3.515 -1.9185) (xy -3.5375 -1.929)
(xy -3.5605 -1.938) (xy -3.5845 -1.946) (xy -3.6085 -1.9525) (xy -3.6325 -1.958) (xy -3.657 -1.9625)
(xy -3.682 -1.9655) (xy -3.707 -1.9675) (xy -3.732 -1.968) (xy -3.818 -1.968) (xy -3.818 -2.507)
(xy -3.8185 -2.532) (xy -3.8205 -2.557) (xy -3.8235 -2.5815) (xy -3.828 -2.606) (xy -3.8335 -2.6305)
(xy -3.84 -2.6545) (xy -3.848 -2.678) (xy -3.8575 -2.701) (xy -3.8675 -2.724) (xy -3.879 -2.746)
(xy -3.8915 -2.7675) (xy -3.905 -2.7885) (xy -3.9195 -2.809) (xy -3.935 -2.8285) (xy -3.9515 -2.847)
(xy -3.969 -2.865) (xy -3.987 -2.882) (xy -4.0065 -2.898) (xy -4.026 -2.913) (xy -4.0465 -2.927)
(xy -4.068 -2.94) (xy -4.09 -2.952) (xy -4.1125 -2.963) (xy -4.1355 -2.9725) (xy -4.159 -2.981) (xy -4.1825 -2.9885)
(xy -4.2065 -2.9945) (xy -4.231 -2.9995) (xy -4.256 -3.003)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "F.Paste")
(uuid "85e1e582-4d28-42b6-8c6d-e8a4bbdb0ffc")
)
(fp_circle
(center -3.4 -0.613)
(end -3.3 -0.613)
(stroke
(width 0.2)
(type default)
)
(fill no)
(layer "Dwgs.User")
(uuid "35b73015-b34a-4a8d-b1a0-3cab199384d8")
)
(fp_circle
(center 3.4 -0.613)
(end 3.5 -0.613)
(stroke
(width 0.2)
(type default)
)
(fill no)
(layer "Dwgs.User")
(uuid "0acd5d30-b941-4b4b-a95a-b68f66017fec")
)
(fp_poly
(pts
(xy -3.154804 -0.661773) (xy -3.192133 -0.751893) (xy -3.261107 -0.820867) (xy -3.351227 -0.858196)
(xy -3.448773 -0.858196) (xy -3.538893 -0.820867) (xy -3.607867 -0.751893) (xy -3.645196 -0.661773)
(xy -3.645196 -0.564227) (xy -3.607867 -0.474107) (xy -3.538893 -0.405133) (xy -3.448773 -0.367804)
(xy -3.351227 -0.367804) (xy -3.261107 -0.405133) (xy -3.192133 -0.474107) (xy -3.154804 -0.564227)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "Edge.Cuts")
(uuid "93982eb2-c212-4ff4-8ccd-e9151c2bbd19")
)
(fp_poly
(pts
(xy 3.645196 -0.661773) (xy 3.607867 -0.751893) (xy 3.538893 -0.820867) (xy 3.448773 -0.858196) (xy 3.351227 -0.858196)
(xy 3.261107 -0.820867) (xy 3.192133 -0.751893) (xy 3.154804 -0.661773) (xy 3.154804 -0.564227) (xy 3.192133 -0.474107)
(xy 3.261107 -0.405133) (xy 3.351227 -0.367804) (xy 3.448773 -0.367804) (xy 3.538893 -0.405133) (xy 3.607867 -0.474107)
(xy 3.645196 -0.564227)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "Edge.Cuts")
(uuid "b0fb7b40-906a-4c72-bce8-049a4c1c5c1b")
)
(fp_poly
(pts
(xy -4.42 -2.6125) (xy -4.22 -2.6125) (xy -4.22 -1.3125) (xy -4.42 -1.3125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "365d2500-512c-46ef-beff-a0b972b8ebd1")
)
(fp_poly
(pts
(xy -4.42 1.8375) (xy -4.22 1.8375) (xy -4.22 3.3375) (xy -4.42 3.3375)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "064af53d-13ff-43f6-a145-21b205f731f5")
)
(fp_poly
(pts
(xy -3.6 -1.5625) (xy -3.6 -1.3625) (xy -4.42 -1.3625) (xy -4.42 -1.5625)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "c0634111-c780-4251-a9ba-73827550f8d0")
)
(fp_poly
(pts
(xy 4.22 -2.6125) (xy 4.42 -2.6125) (xy 4.42 -1.3125) (xy 4.22 -1.3125)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "c5c769da-4417-47da-8da0-184581d08d0e")
)
(fp_poly
(pts
(xy 4.22 1.8375) (xy 4.42 1.8375) (xy 4.42 3.3375) (xy 4.22 3.3375)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "90520a4d-140b-42cc-aa87-3e6812748aa5")
)
(fp_poly
(pts
(xy 4.42 -1.5625) (xy 4.42 -1.3625) (xy 3.6 -1.3625) (xy 3.6 -1.5625)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.1")
(uuid "03134a66-cd0f-4285-9a1b-a9f03482f5fc")
)
(fp_line
(start -4.45 -2.591)
(end 4.45 -2.591)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "b9e6c133-f5d3-4ea0-95e9-57164bc6eee9")
)
(fp_line
(start -4.45 5.309)
(end -4.45 -2.591)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "be50ba23-22f3-4235-a1cc-3b3a507b6e8c")
)
(fp_line
(start 4.45 -2.591)
(end 4.45 5.309)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "f1e9e7a2-bdce-45fd-95ea-ff0c9b82e646")
)
(fp_line
(start 4.45 5.309)
(end -4.45 5.309)
(stroke
(width 0.051)
(type default)
)
(layer "User.2")
(uuid "164f4531-886e-4362-bb7d-6e246cfe4466")
)
(fp_poly
(pts
(xy -4.673567 -3.060461) (xy -4.706039 -3.092933) (xy -4.751961 -3.092933) (xy -4.784433 -3.060461)
(xy -4.784433 -3.014539) (xy -4.751961 -2.982067) (xy -4.706039 -2.982067) (xy -4.673567 -3.014539)
)
(stroke
(width 0)
(type default)
)
(fill yes)
(layer "User.3")
(uuid "5e5bec19-e0fc-4fa2-bd4e-78020d0a4ad8")
)
(pad "3" thru_hole oval
(at 4.32 2.5875)
(size 1 2.3)
(drill oval 0.6 1.9)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "4c0096c3-57ef-4ce9-a818-6c12f238202b")
)
(pad "4" thru_hole oval
(at -4.32 2.5875)
(size 1 2.3)
(drill oval 0.6 1.9)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "b0205095-5b55-491a-b7a0-2750a78f073b")
)
(pad "25" thru_hole oval
(at -4.318 -2.032)
(size 1 1.95)
(drill oval 0.5 1.5)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "d1f91875-3de4-49ba-9053-1629fe3eb265")
)
(pad "25" thru_hole oval
(at -4.082 -1.468 90)
(size 1 1.7)
(drill oval 0.5 1.2)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "0cf8d292-e9ee-4e3c-81f0-daadbdbbee0f")
)
(pad "25" thru_hole oval
(at 4.046 -1.468 90)
(size 1 1.7)
(drill oval 0.5 1.2)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "f8b787e2-0d51-4b0a-a4d6-6c3d34b80d1b")
)
(pad "25" thru_hole oval
(at 4.318 -2.032)
(size 1 1.95)
(drill oval 0.5 1.5)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "63de83df-de06-41a0-84ad-ce24515b8adb")
)
(pad "A1" smd rect
(at -2.625 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "99be108a-660f-421a-909f-fcd9a228eb81")
)
(pad "A2" smd rect
(at -2.125 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "82639158-46ef-478f-adca-48b36a03172b")
)
(pad "A3" smd rect
(at -1.625 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "cbf1b67c-0c17-43d8-9f1e-8fa80d33c230")
)
(pad "A4" smd rect
(at -1.125 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "3b8d20c8-f74c-460b-a2b5-4b3474ff1428")
)
(pad "A5" smd rect
(at -0.625 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "b783a52b-4790-48c7-ab3b-f86ade6b580b")
)
(pad "A6" smd rect
(at -0.125 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "67d3421c-9e3c-4e70-a79a-bd7876cafe5c")
)
(pad "A7" smd rect
(at 0.375 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "64f3ab1b-33b2-40cf-888f-5879026010d8")
)
(pad "A8" smd rect
(at 0.875 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "18ee12cd-21a2-4955-a2ac-ffd0a43eedb8")
)
(pad "A9" smd rect
(at 1.375 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "6f564c70-4ed6-48d9-abc0-b0b911e35ffb")
)
(pad "A10" smd rect
(at 1.875 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "c785064e-e9f6-4296-8ee5-a57657c085b7")
)
(pad "A11" smd rect
(at 2.375 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "dd0a8c5a-1142-4333-8a5a-62d82dfe20da")
)
(pad "A12" smd rect
(at 2.875 -2.5875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "18481ad9-d4be-4931-adc6-4acc9c4ec500")
)
(pad "B1" smd rect
(at 2.625 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "a5cd1b0f-c331-4c58-9d72-fbc21a5ae09e")
)
(pad "B2" smd rect
(at 2.125 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "a6894b7c-c1e8-4aba-a356-2933bec301b9")
)
(pad "B3" smd rect
(at 1.625 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "b61734b4-a351-4d8a-b471-268a2e7ecbee")
)
(pad "B4" smd rect
(at 1.125 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "498a3858-c423-4b4c-a318-f486aa1d81aa")
)
(pad "B5" smd rect
(at 0.625 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "f1f54aa0-9f2e-4ad9-b97f-5b01ab73ff53")
)
(pad "B6" smd rect
(at 0.125 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "46ccfd25-5b33-4cd7-b757-5eba35952bd3")
)
(pad "B7" smd rect
(at -0.375 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "29422391-1473-49b3-9f7b-67129d88746f")
)
(pad "B8" smd rect
(at -0.875 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "8d2d02f5-899b-456b-b25f-037c66560a02")
)
(pad "B9" smd rect
(at -1.375 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "ca4b6301-e763-49a3-95f5-0972fa908515")
)
(pad "B10" smd rect
(at -1.875 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "e1ecc519-4625-4061-9c28-10051b84a412")
)
(pad "B11" smd rect
(at -2.375 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "074b4931-90e1-4d9f-a794-ef34bbd30753")
)
(pad "B12" smd rect
(at -2.875 -1.2875)
(size 0.3 0.95)
(layers "F.Cu" "F.Mask" "F.Paste")
(uuid "49c3613a-a608-4b6e-9257-afcd9a44bcd5")
)
(embedded_fonts no)
)

View File

@@ -0,0 +1,114 @@
(footprint "Untitled"
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(layer "F.Cu")
(property "Reference" "REF**"
(at 0 4 0)
(unlocked yes)
(layer "F.SilkS")
(uuid "aa81911f-74f3-44b4-8c0f-b08df82416dd")
(effects
(font
(size 1 1)
(thickness 0.1)
)
)
)
(property "Value" "Untitled"
(at 0 5.5 0)
(unlocked yes)
(layer "F.Fab")
(uuid "50bcbb39-a675-4170-a2a9-d1a324fdfcaa")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "e21422cb-a050-4bd9-83f2-27764667128c")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "b5458599-94cf-469c-983a-74769079b2b0")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(attr smd)
(fp_poly
(pts
(xy -3.2 -2.45) (xy -3.2 2.45) (xy 3.2 2.45) (xy 3.2 -1.45) (xy 2.2 -2.45)
)
(stroke
(width 0.1)
(type solid)
)
(fill no)
(layer "F.SilkS")
(uuid "6a77a472-93c2-4b51-92f7-704b1df15ea3")
)
(fp_text user "${REFERENCE}"
(at 0 7 0)
(unlocked yes)
(layer "F.Fab")
(uuid "b51c74cb-e61e-4075-a081-d54a7d91c7ab")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(pad "1" thru_hole circle
(at -1.4 1.25)
(size 1.8 1.8)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "38e4062f-e468-4b7e-ba69-d44c203e51d0")
)
(pad "2" thru_hole circle
(at -1.4 -1.25)
(size 1.8 1.8)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "da1ed95e-5dc3-4356-a1d6-3c758a75af99")
)
(pad "3" thru_hole circle
(at 1.4 -1.25)
(size 1.8 1.8)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "b338b8f3-f557-49ee-8333-131c1f2eabec")
)
(pad "4" thru_hole circle
(at 1.4 1.25)
(size 1.8 1.8)
(drill 1)
(layers "*.Cu" "*.Mask")
(remove_unused_layers no)
(uuid "34aa61fd-127c-45e1-9719-bb74301a49d4")
)
(embedded_fonts no)
)

178
mozc-dial/board/README.md Normal file
View File

@@ -0,0 +1,178 @@
# Board Assembly Guide
For the entire keyboard assembly, please refer to the [Build Guide](../buildguide.md).
## Common to All Editions
### Sensor Board (per board)
#### Bill of Materials
| Ref | Description | Qty | Notes |
|---|---|---|---|
| R1,R3,R5 | Resistor 330Ω | 3 | Through-hole or surface-mount |
| R2,R4,R6 | Not populated | - | |
| U1-3 | Reflective photo sensor RPR-220 | 3 | (e.g., [Reflective Photo Sensor (Photoreflector) RPR-220](https://akizukidenshi.com/catalog/g/g111401/)) |
| J1 | 2.54mm pitch 1x5 pin header | 1 | L-shaped is desirable (e.g., [Pin Header (Male L-type) 1x40 (40P)](https://akizukidenshi.com/catalog/g/g101627/)) |
#### Assembly
This explanation covers the case where all 3 bits are populated. However, for the 9-dial edition, some sensors may only require 1 or 2 bits. In that case, populate only the required number of components from the top (U1). For details, follow the instructions for the 9-dial edition.
Also, before making everything, it is recommended to check if the Gray code plate values can be read properly to reduce rework in case of problems. If it doesn't work well, populate resistors on R2, R4, and R6. For details, see [Reading Sensor Adjustment](#reading-sensor-adjustment).
![Sensor RPR-220](../images/sensor_rpr.webp)
First, attach the photo sensors from the side where the U1, U2, and U3 markings are visible. Pay close attention to the orientation. The upper right corner is chamfered, so check the marking and the shape of the sensor to ensure they are in the same orientation. After correct placement, cut off the excess leads from the back and solder them.
![Sensor Resistors](../images/sensor_regs.webp)
On the back side, there are places to attach three resistors on the `(SENSOR)` side and three on the `(LED)` side, as shown in the figure. Both through-hole and chip resistors can be used, so use the parts of your choice. However, even for through-hole types, it is more stable to mount them without passing them through the holes when attaching the dial.
As mentioned earlier, the resistors depend on the adjustment, but it is recommended to leave the `(SENSOR)` side unpopulated and install 330Ω resistors on the `(LED)` side as a standard.
For the 1x5 pin header, using an L-shaped one will fit well in the case.
During assembly, pay attention to the `⇧Center` mark. The inner circumference of the dial will be in the direction of the arrow.
## 1-Dial Edition
This is an introductory edition that uses commercially available general-purpose parts other than the sensor board.
### Parts
- Raspberry Pi Pico: 1
- Sensor board (3-bit populated): 2
- [Unipolar Stepping Motor 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/): 1
- [Stepping Motor Drive Module [M2003S]](https://www.aitendo.com/product/12366): 1
- Wiring materials: as needed
### Assembly
#### Sensor Board
Assemble two 3-bit populated sensor boards as described in the common section for all dial editions. We will call one `SENSOR L` and the other `SENSOR H`.
Although not a clean example as it was used for experiments, here are examples of mounting both through-hole and chip resistors.
![Sensor PCB Pair](../images/sensor_pair.webp)
Each board's pin header has five terminals: `V`, `1`, `2`, `3`, and `G`.
Of these, `V` connects to the 3.3V power supply, but the Raspberry Pi Pico we are using has only one 3.3V output terminal. Therefore, connect the terminal pair where `V` passes on the board, as shown by the red wiring in the photo.
In the diagram for the sensor board, the terminals inside the rectangle labeled `V`, that is, the six outer locations of the resistor patterns, are `V`.
The upper sensor board is `SENSOR L` for the inner circumference, and the lower sensor board is `SENSOR H` for the outer circumference.
Since `V` is interconnected, the `V` at the top-most, inner-most terminal is not needed for this connection.
#### Stepping Motor Drive Module
![Motor Driver](../images/motor_driver.webp)
The Raspberry Pi Pico's GPIO cannot drive the motor directly, so the 1-dial edition uses a commercially available drive module. Any sink-type driver other than the one specified here will work, and you can also build your own by referring to the circuit of the 9-dial edition.
Taking the one mentioned here as an example, `IN1` to `IN4` enclosed in green are the terminals that receive the outputs `1` to `4` from the Raspberry Pi Pico. `GND` enclosed in black and `VCC` enclosed in red are `G` and `V` respectively.
The motor draws 5V power from the USB bus via the Raspberry Pi Pico. It requires about 0.3A, so the firmware for the 1-dial edition requests the 0.5A upper limit for USB micro.
The white terminal is used for connection with the stepping motor.
#### Raspberry Pi Pico
![Raspberry Pi Pico - Pin Headers](../images/pico_pinheader.webp)
Attach L-shaped pin headers to a commercially available Raspberry Pi Pico as shown in the photo.
The three DEBUG pins at the bottom of the photo are optional and only needed if you need to develop and debug the firmware.
![Raspberry Pi Pico - Pin Out](../images/pico_pinout.webp)
The pins used in the 1-dial edition are assigned as shown in the diagram. Connect to SENSOR L, SENSOR H, and MOTOR according to this diagram. When debugging, the standard output is output from TX via UART.
### Firmware Flashing
Follow the general method for the Raspberry Pi Pico. Connect the Raspberry Pi Pico to your PC while holding down the `BOOT` button on the board. A drive named `RPI-RP2` will appear. Copy `firmware/prebuilt/one_dial.uf2` into this drive. If the flashing is successful, the drive will disappear. Your OS may show a warning, but this is not a problem. From now on, when you connect this device to your PC without holding down the `BOOT` button, it will be recognized as a USB keyboard.
## 9-Dial Edition
### Parts
- Main board: 1
- Sensor board (3-bit populated): 6
- Sensor board (2-bit populated): 4
- Sensor board (1-bit populated): 1
- [Unipolar Stepping Motor 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/): 9
- Wiring materials: as needed
### Main Board
#### Bill of Materials
| Ref | Description | Qty | Notes |
|---|---|---|---|
| C101 | Capacitor 10uF | 1 | |
| C102-121,C202-208,C212,C302-308,C312 | Capacitor 100nF | 36 | |
| C201,C209,C301,C309 | Capacitor 1uF | 4 | |
| C210,C211,C310,C311 | Capacitor 20pF | 4 | |
| J102-103 | 2.54mm pitch 1x3 pin header | 2 | |
| J106-J125 | 2.54mm pitch 1x5 pin header | 20 | |
| J201,J301 | USB TYPE C Receptacle | 2 | |
| R201-202,R301-302 | Resistor 27.4Ω | 4 | |
| R203-204,R303-304 | Resistor 5.1KΩ | 4 | |
| R205,R207,R305,R307 | Resistor 1KΩ | 4 | |
| R206,R306 | Resistor 10KΩ | 2 | |
| SW101,SW201,SW301 | Switch | 3 | |
| U101 | AMS1117 3.3V Regulator | 1 | |
| U102-103,U105,U107,U109 | TBD62083AFNG | 5 | |
| U104,U106,U108,U110 | 74HC238 | 4 | |
| U201,U301 | RP2040 | 2 | |
| U202,U302 | W25Q128JVS | 2 | |
| Y201,Y301 | 12MHz Crystal Oscillator | 2 | |
| N/A | Jumper pin | 2 | |
#### Assembly
Currently, we have released up to the schematics. We will release the Gerber data soon, so please look forward to it!
![Coming Soon!!](../images/main_pcb_preview.webp)
### Assembly
Attach 1x5 L-shaped pin headers for connecting to the sensor boards to `SENSOR A-L`, `SENSOR A-H`, `SENSOR B`, `SENSOR C`, `SENSOR D`, `SENSOR E`, `SENSOR F`, `SENSOR G`, `SENSOR H`, `SENSOR I-L`, and `SENSOR I-H`.
Attach 1x5 L-shaped pin headers for connecting to the motors to `MOTOR A`, `MOTOR B`, `MOTOR C`, `MOTOR D`, `MOTOR E`, `MOTOR F`, `MOTOR G`, `MOTOR H`, and `MOTOR I`.
Attach a 1x3 pin header to the location marked `U1 PWR U2` and short the `U1` and `PWR` positions with a jumper pin.
Attach a 1x3 pin header to the location marked `USB V_MTR ALT` and short the `USB` and `V_MTR` positions with a jumper pin.
### Sensor Board
Referring to the [Common to All Dial Editions](#common-to-all-dial-editions) section, prepare a total of 11 sensor boards with the following populated bit widths. There is no problem other than an increase in standby power consumption if you populate more bits, so it is also fine to prepare all of them with a 3-bit width.
| Ref | Populated Bit Width |
|---|---|
| SENSOR A-L | 3 |
| SENSOR A-H | 3 |
| SENSOR B | 2 |
| SENSOR C | 3 |
| SENSOR D | 2 |
| SENSOR E | 3 |
| SENSOR F | 3 |
| SENSOR G | 2 |
| SENSOR H | 2 |
| SENSOR I-L | 3 |
| SENSOR I-H | 1 |
## Photo Sensor Adjustment
The photo sensor readings should not vary greatly as they are taken inside the case, but differences may arise due to the brightness of the 3D printing material or the quality of the paper on which the Gray code is printed. If the dial reading is unstable or the rotation does not stop, the sensor value change range may not be within the expected range. In that case, please adjust the sensor paying attention to the following points.
### Reading with RP2040
The sensor value is processed as a digital value by GPIO. Therefore, it needs to be adjusted so that HIGH is in the range of 2.0 - 3.3[V] and LOW is in the range of 0.0 - 0.3[V] when operating at 3.3V.
### LED Sensor Adjustment
If the current flowing through the LED is small, the light emission will be weak, and the amount of light read by the sensor will be insufficient. This will result in higher readings, and the voltage difference between white and black will not be clear. In that case, try changing the three resistors on the `(LED)` side to weaker ones. The standard is adjusted with 330Ω, but with the photo sensor RPR-220 used this time, it will stay within the rated value even if you lower it to about 40Ω. However, it will greatly affect the standby power consumption, so if possible, please adjust with the resistor on the reading sensor side.
### Reading Sensor Adjustment
You can shift the read voltage value by changing the pull-up resistance of the reading sensor. However, if you change it too much, the voltage difference between white and black will become less clear. You can adjust it by populating the three resistors on the `(SENSOR)` side. The standard firmware expects a pull-up of about 50-80KΩ by the RP2040's pull-up. If the voltage does not rise sufficiently with black, it is better to pull-up more strongly with an additional resistor. By following the [Firmware Development Guide](../firmware/README.md) and disabling the RP2040's pull-up, the resistor populated here will become the pull-up resistor. If you do not disable it, it will be pulled up by the combined resistance in parallel. The calculation is a bit complicated, but it may be easier to pull-up in parallel with an additional resistor without changing the firmware if you want to pull-up more strongly.

View File

@@ -0,0 +1,187 @@
# 基板組立ガイド
キーボードの組み立て全体については[ビルドガイド](../buildguide_ja.md)を参照してください。
## 各ダイヤル版共通
### センサー基板(1枚あたり)
#### 部品表
参照名 | 内容 | 数 | 備考
-------- | -------------------- | --- | ---
R1,R3,R5 | 抵抗 330Ω | 3 | スルーホール用でも表面実装用でも可
R2,R4,R6 | 未実装 | - |
U1-3 | 反射型フォトセンサー RPR-220 | 3 | (例:[反射型フォトセンサー(フォトリフレクター) RPR-220](https://akizukidenshi.com/catalog/g/g111401/)
J1 | 2.54mmピッチ 1x5 ピンヘッダー | 1 | L字のものが望ましい[ピンヘッダー (オスL型) 1×40 (40P)](https://akizukidenshi.com/catalog/g/g101627/)
#### 実装
この説明では3ビット分全て実装する場合について説明していますが、9ダイヤル版ではセンサーによっては1ビットまたは2ビット分の実装で良い場合があります。その場合は、上(U1)から順に必要な数だけ実装してください。詳しくは9ダイヤル版の説明に従ってください。
また、すべて制作する前に、実際にグレイコード板の値が適切に読み取れるか確認し、問題がない事を確認してから作ると万が一の際の手間が軽減できます。うまくいかない場合にはR2,R4,R6に抵抗を実装します。詳しくは[読み取りセンサー調整](#読み取りセンサー調整)を確認してください。
![Sensor RPR-220](../images/sensor_rpr.webp)
まずはU1、U2、U3の印刷が見える方から、フォトセンサーを取りつけます。向きに十分気をつけて下さい。右上の角が面取りされているので、印刷面とセンサーの形状を見て、同じ向きになるよう設置してください。正しく設置したら、裏側から不要な足を切り取りハンダ付けします。
![Sensor Registors](../images/sensor_regs.webp)
裏面はこの図のように`(SENSOR)`側に3つ、`(LED)`側に3つの抵抗を取り付けられるようになっています。抵抗はスルーホール用のものと、チップ抵抗と、いずれのタイプでも取り付けられるようになっているので、お好みいのパーツを使って取り付けてください。ただし、スルーホール用の物でも、穴には通さずに取り付けた方がダイヤル取り付け時に安定します。
抵抗については前述の通り調整に依存しますが、標準では`(SENSOR)`側は未実装、`(LED)`側に330Ωの抵抗を取り付ける事を推奨します。
また、1x5のピンヘッダーについては、L字の物を使うとケースにうまく収まります。
組立時には`⇧Center`マークに注意してください。ダイヤル内周側が矢印の方向になります。
## 1ダイヤル版
センサー基板以外は市販の汎用部品を使う入門版です。
### 部品
- Raspberry Pi Pico: 1個
- センサー基板3bit実装: 2個
- [ユニポーラー ステッピングモーター 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/):
1個
- [ステッピングモータドライブモジュール [M2003S]](https://www.aitendo.com/product/12366): 1個
- 配線材: 適宜
### 組み立て
#### センサー基板
各ダイヤル版共通で説明したセンサー基板を、3bit実装で2つ組み立てます。 1つを`SENSOR L`、もう1つを`SENSOR H`と呼ぶことにします。
実験時に使用したもので見本としては綺麗なものではありませんが、スルーホール型の抵抗とチップ抵抗それぞれの実装例がこちらになります。
![Sensor PCB Pair](../images/sensor_pair.webp)
それぞれに基板のピンヘッダーには`V``1``2``3``G`の5つの端子があります。
このうち`V`は3.3V電源へ繋ぐのですが、今回利用するRaspberry Pi
Picoには3.3V出力端子は1つしかありませんので、写真の赤い配線のように基板上で`V`が通っている端子ペアを接続します。
センサー基板の説明で上げた図において`V`と書かれた長方形の内側にある端子、つまり抵抗用パターンのうち外側の6箇所が`V`になっています。
上側のセンサー基板が`SENSOR L`で内周側、下側のセンサー基板が`SENSOR H`で外周側になります。
`V`を相互接続したので、一番上の最も内側にあたる端子の`V`は今回接続不要となります。
#### ステッピングモータドライブモジュール
![Motor Driver](../images/motor_driver.webp)
Raspberry Pi
PicoのGPIOでは直接モーターをドライブできないため、1ダイヤル版では市販のドライブモジュールを流用します。シンクタイプのドライバなら今回指定したもの以外でも構いませんし、9ダイヤル版の回路を参考に自作する事も可能です。
今回参考に上げた物を例にすると、緑で囲んだ`IN1``IN4`がRaspberry Pi
Picoからの出力の`1``4`を受ける端子になります。黒で囲んだ`GND`と赤で囲んだ`VCC`がそれぞれ`G``V`です。
モーターの方はUSBバスから5Vの電源をRaspberry Pi
Pico経由で引き込みます。0.3Aくらい必要となるため、1ダイヤル版のファームウェアではUSB microでの上限である0.5Aを要求しています。
白い端子はステッピングモータとの接続に使います。
#### Raspberry Pi Pico
![Raspberry Pi Pico - Pin Headers](../images/pico_pinheader.webp)
市販のRaspberry Pi Picoに対して、写真のようにL字のピンヘッダーを取り付けます。
写真で下の方にあるDEBUG用のピン3つについては、ファームウェアの開発とデバッグが必要な場合のみで構いませんので、お好みで取り付けてください。
![Raspberry Pi Pico - Pin Out](../images/pico_pinout.webp)
1ダイヤル版で使うピンについては、図のような割り当てになっています。この図に従って、SENSOR L、SENSOR
H、MOTORと接続します。デバッグ時の標準出力をUARTから出力する場合はTXから出力されます。
### ファームウェア書き込み
Raspberry Pi Picoの一般的な方法に従います。Raspberry Pi
Pico基板上の`BOOT`ボタンを押しながらPCに接続すると`RPI-RP2`という名前のドライブが出現するので、`firmware/prebuilt/one_dial.uf2`をこのドライブの中にコピーします。正しく書き込みができた場合ドライブが消えます。OSによって警告が表示されますが問題ありません。これ以降、このデバイスを`BOOT`ボタンを押さずにPCに接続した場合は、USBキーボードとして認識されるようになります。
## 9ダイヤル版
### 部品
- メイン基板: 1個
- センサー基板3bit実装: 6個
- センサー基板2bit実装: 4個
- センサー基板1bit実装: 1個
- [ユニポーラー ステッピングモーター 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/):
9個
- 配線材: 適宜
### メイン基板
#### 部品表
参照名 | 内容 | 数
------------------------------------ | ----------------------- | ---
C101 | コンデンサ 10uF | 1
C102-121,C202-208,C212,C302-308,C312 | コンデンサ 100nF | 36
C201,C209,C301,C309 | コンデンサ 1uF | 4
C210,C211,C310,C311 | コンデンサ 20pF | 4
J102-103 | 2.54mmピッチ 1x3 ピンヘッダ | 2
J106-J125 | 2.54mmピッチ 1x5 ピンヘッダ | 20
J201,J301 | USB TYPE C Receptacle | 2
R201-202,R301-302 | 抵抗 27.4Ω | 4
R203-204,R303-304 | 抵抗 5.1KΩ | 4
R205,R207,R305,R307 | 抵抗 1KΩ | 4
R206,R306 | 抵抗 10KΩ | 2
SW101,SW201,SW301 | スイッチ | 3
U101 | AMS1117 3.3V レギュレーター | 1
U102-103,U105,U107,U109 | TBD62083AFNG | 5
U104,U106,U108,U110 | 74HC238 | 4
U201,U301 | RP2040 | 2
U202,U302 | W25Q128JVS | 2
Y201,Y301 | 12MHz Crystal Oscilator | 2
N/A | ジャンパーピン | 2
#### 実装
現在、回路図まで公開しています。近日中にガーバーデータを公開しますのでお楽しみに!
![Coming Soon!!](../images/main_pcb_preview.webp)
### 組み立て
`SENSOR A-L``SENSOR A-H``SENSOR B``SENSOR C``SENSOR D``SENSOR E``SENSOR F``SENSOR G``SENSOR H``SENSOR I-L``SENSOR I-H`それぞれに、センサー基板との接続に用いる1x5のL字型ピンヘッダを取り付けます。
`MOTOR A``MOTOR B``MOTOR C``MOTOR D``MOTOR E``MOTOR F``MOTOR G``MOTOR H``MOTOR I`についてはモーターとの接続に用いる1x5のL字型ピンヘッダを取り付けます。
`U1 PWR U2`と書かれた箇所に1x3のピンヘッダを取り付け、ジャンパーピンで`U1``PWR`の位置をショートします。
`USB V_MTR ALT`と書かれた箇所に1x3のピンヘッダと取り付け、ジャンパーピンで`USB``V_MTR`の位置をショートします。
### センサー基板
[各ダイヤル版共通](#各ダイヤル版共通)の説明を参照して、合計11個のセンサー基板をそれぞれ以下の実装ビット幅で用意します。多めに実装する分には待機時消費電力の増加以外は問題ないので、全てを3ビット幅で用意してしまっても問題はありません。
参照名 | 実装ビット幅
---------- | ------
SENSOR A-L | 3
SENSOR A-H | 3
SENSOR B | 2
SENSOR C | 3
SENSOR D | 2
SENSOR E | 3
SENSOR F | 3
SENSOR G | 2
SENSOR H | 2
SENSOR I-L | 3
SENSOR I-H | 1
## フォトセンサーの調整について
フォトセンサーの読み取り値は、ケース内での読み取りになるため大きな差は出にくいはずですが、3Dプリント素材の明るさの違いや、グレイコードを印刷した紙の質などによって違いが出てくる可能性があります。ダイヤルの読み取りが安定しない、回転が止まらないなどの症状が出ている場合にはセンサー値の変化範囲が想定内に収まっていない可能性がありますので、以下の点に注意してセンサーの調整をしてください。
### RP2040での読み取り
センサーの値はGPIOでデジタル値として処理しています。そのため、3.3V稼働時にHIGHは2.0 - 3.3[V]、LOWは0.0 -
0.3[V]の範囲に収まるよう調整されている必要があります。
### LEDセンサー調整
LEDに流れる電流が少ないと発光も弱くなり、センサーで読み取る光量も不足します。そのため、読み取り値が高くなり、白と黒で電圧差がはっきりとでなくなってきます。その場合、`(LED)`と書かれた側にある3つの抵抗を弱いものに変えてみてください。標準では330Ωで調整していますが、今回用いているフォトセンサーRPR-220では40Ωくらいまで下げても定格内に収まります。ただし、待機時の消費電力に大きく影響しますので、可能なら読み取りセンサー側の抵抗で調整してください。
### 読み取りセンサー調整
読み取りセンサーのpull-up抵抗値を変える事で、読み取る電圧値をシフトさせる事ができます。ただし、大きく変えすぎるとやはり白と黒の電圧差が出にくくなります。`(SENSOR)`と書かれた側にある3つの抵抗を実装する事で調整できます。標準のファームウェアではRP2040のpull-upにより、およそ50-80KΩでpull-upされる事を期待しています。黒で十分に電圧が上がらない場合は、追加の抵抗でより強くpull-upすると良いでしょう。[ファームウェア開発ガイド](../firmware/README_ja.md)に従いRP2040のpull-upを無効化すると、ここに実装した抵抗がそのままpull-up抵抗になります。無効化しない場合は並列接続での合成抵抗でpull-upする事になります。計算が少し煩雑ですが、より強くpull-upする分にはファームウェアを変えずに追加の抵抗で並列でpull-upする方が簡単かもしれません。

Binary file not shown.

View File

@@ -0,0 +1,4 @@
(fp_lib_table
(version 7)
(lib (name "Gboard")(type "KiCad")(uri "${KIPRJMOD}/../Library.pretty")(options "")(descr ""))
)

View File

@@ -0,0 +1,131 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": false,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"footprint_values",
"footprint_references",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"locked_item_shadows",
"conflict_shadows",
"shapes"
],
"visible_layers": "00000000_00000000_0fffd575_ffffffff",
"zone_display_mode": 1
},
"git": {
"repo_password": "",
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "main.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [
156,
141,
103,
71,
103,
103,
103,
74,
103,
10
],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": 0
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"symbols": true,
"text": true,
"wires": true
}
}
}

View File

@@ -0,0 +1,675 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 2.54
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "ignore",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "ignore",
"text_height": "ignore",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.1,
0.15,
0.2,
0.3,
0.4,
0.5,
0.8,
1.0,
1.5,
2.0
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "ignore",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "main.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.18,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "main.dsn",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": true,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "LCSC",
"name": "LCSC",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"meta": {
"version": 0
},
"model_mode": 4,
"workbook_filename": ""
},
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"8101b443-17cd-4771-8577-cc1ed4ee7f87",
"Root"
],
[
"529e70e4-69ee-4815-a64b-efeb5a5b6370",
"RP2040_1"
],
[
"2f21e0b6-5a6a-4f16-a345-da73c4df9dc5",
"RP2040_2"
]
],
"text_variables": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
(sym_lib_table
(version 7)
(lib (name "Gboard")(type "KiCad")(uri "${KIPRJMOD}/../Library.kicad_sym")(options "")(descr ""))
)

View File

@@ -0,0 +1,4 @@
(fp_lib_table
(version 7)
(lib (name "Gboard")(type "KiCad")(uri "${KIPRJMOD}/../Library.pretty")(options "")(descr ""))
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
{
"board": {
"active_layer": 7,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"footprint_values",
"footprint_references",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"locked_item_shadows",
"conflict_shadows",
"shapes"
],
"visible_layers": "ffffffff_ffffffff_ffffd575_ffffffff",
"zone_display_mode": 0
},
"git": {
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "sensors.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [
10,
10,
10,
10,
10,
10,
10,
10,
10,
10
],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": 0
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"symbols": true,
"text": true,
"wires": true
}
}
}

View File

@@ -0,0 +1,646 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 1.0,
"height": 2.0,
"width": 2.0
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "error",
"copper_edge_clearance": "error",
"copper_sliver": "error",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "error",
"extra_footprint": "error",
"footprint": "error",
"footprint_filters_mismatch": "error",
"footprint_symbol_mismatch": "error",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "error",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "error",
"lib_footprint_mismatch": "error",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "error",
"missing_courtyard": "ignore",
"missing_footprint": "error",
"net_conflict": "error",
"nonmirrored_text_on_back_layer": "error",
"npth_inside_courtyard": "error",
"padstack": "warning",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "error",
"text_on_edge_cuts": "error",
"text_thickness": "error",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "error",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.005,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.25,
0.5,
1.0
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "sensors6.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
},
{
"group_by": false,
"label": "Description",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "",
"sort_asc": true,
"sort_field": "Reference"
},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"dbb8120e-03a7-447b-932f-d5d3835f031f",
"Root"
]
],
"text_variables": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
(sym_lib_table
(version 7)
(lib (name "Gboard")(type "KiCad")(uri "${KIPRJMOD}/../Library.kicad_sym")(options "")(descr ""))
)

168
mozc-dial/buildguide.md Normal file
View File

@@ -0,0 +1,168 @@
# Build Guide
![Gboard DIY](./images/diy.webp)
[Read the project overview first](./README.md)
## File List
- stls/ : STL files for the case
- stls/one_dial/ : For the 1-dial edition
- stls/full/ : For the 9-dial edition (Stay tuned!)
- pattern/ : Encoder pattern files
- board/ : KiCad schematics and board layout data
- board/gerber/ : Gerber data for PCB production and assembly
- firmware/ : Firmware development resources for Raspberry Pi Pico C/C++ SDK
- firmware/prebuilt : Pre-built firmware binaries
## Assembly
We have prepared a 1-dial edition, which is easier to assemble with only one dial, and a full edition with nine dials.
### 1-Dial Edition
![1-Dial Edition](./images/one-dial.webp)
#### Required Parts
- 3D printed parts (1 of each)
- Bottom case (case_bottom.stl)
- Top case (case_top.stl)
- Dial (dial.stl)
- Dial cap (dial_cap.stl)
- Encoder (encoder.stl)
- Clutch (clutch.stl)
- Fixing cover (cover.stl, print in a color close to white, such as gray)
- Triple claw (end3.stl)
- Encoder pattern pattern/one_dial.svg
- Screws
- 12 self-tapping screws, 2mm diameter, 4mm length
- 2 washers
- Electronic parts ([see the board directory for details](./board/README.md))
- 1 [Firmware installed](./firmware/README.md) Raspberry Pi Pico
- 2 assembled sensor boards
- 1 [Unipolar Stepping Motor 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/)
- 1 [Motor driver module](https://www.aitendo.com/product/12366)
- Wiring materials: as needed
#### Step 1: Create the Encoder
Print the [pattern file](./pattern/one_dial.svg) on copy paper at actual size.
Place the encoder part on top with the printed side down, glue it, and cut around the perimeter and the center.
Align the ◆ mark on the encoder part with the ▼ mark on the paper.
![Align the marks](./images/encoder-mark.webp)
![Assembled encoder](./images/encoder.webp)
#### Step 2: Assemble the Cover Module
The cover needs to be printed in a non-transparent color because it uses a photosensor.
In the prototype, we used gray PLA.
You may need to adjust the resistance of the sensor board for other colors.
Screw the two sensor boards to the back.
Also, screw the stepping motor to the back.
We call the sensor board near the motor as "Sensor L" board and other one as "Sensor H" board.
![Back side](./images/cover-back.webp)
Attach the clutch to the motor shaft protruding from the front.
![Front side](./images/cover-front.webp)
#### Step 3: Attach to the Case
From the back, attach the encoder and then the cover.
Refer to the photo below for the orientation of the cover.
![Assembly](./images/assemble.webp)
From the front, attach the dial and cap to the shaft.
There are small marks on the dial and shaft, so align them.
![Marks on the dial](./images/mark-on-encoder.webp)
Attach the dial cap from the top and secure it with a screw.
![Overall view](./images/one-dial.webp)
#### Step 4: Wiring
Attach the sensor board and motor driver to the Raspberry Pi Pico.
![Wiring](./images/one-dial-connection.png)
Please check [board assembly guide](./board/README.md) for details.
Secure the board to the edge of the bottom case with double-sided tape or similar.
### 9-Dial Edition
**The 9-dial edition is currently under adjustment.**
![9-Dial Edition](./images/nine-dial.webp)
#### Required Parts
- 3D printed parts
- 1 each of left and right bottom cases
- 1 each of left and right top cases
- 9 dials (including the enter key)
- 9 dial caps
- 5 types of encoders, 9 in total
- 1 for 6 bits
- 1 for 4 bits
- 3 for 3 bits
- 3 for 2 bits
- 1 for 1 bit
- 9 clutches
- 2 types of fixing covers, 9 in total
- 7 small
- 2 large
- 3 types of over-rotation prevention claws, 9 in total
- 8 small
- 1 triple
- 9 types of encoder patterns
- Screws
- About 100 self-tapping screws, 2mm diameter, 4mm length
- 18 washers
- A few M3 bolts and nuts
- PCB
- 11 sensor boards
- 1 main board (assembled)
- Electronic parts ([see the board directory for details](./board/README.md))
- 1 assembled main board with [2 firmware installed](./firmware/README.md)
- 11 assembled sensor boards
- 9 [Unipolar Stepping Motors 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/)
- Wiring materials: as needed
### Assembly
#### Step 1: Connect Left and Right
Connect the left and right bottom cases with four M3 screws.
![Connect left and right](./images/connect-left-and-right.webp)
#### Step 2: Create the Encoder
Same as the 1-dial edition.
#### Step 2: Assemble the Cover Module
Basically the same as the 1-dial edition.
Attach only one sensor board to seven of the covers.
Attach two sensor boards to two of the covers.
#### Step 3: Attach to the Case
Basically the same as the 1-dial edition.
#### Step 4: Wiring
Wire the motor and sensor board to the dedicated main board.

164
mozc-dial/buildguide_ja.md Normal file
View File

@@ -0,0 +1,164 @@
# ビルドガイド
![Gboard DIY](./images/diy.webp)
[プロジェクトの概要を先にどうぞ](./README_ja.md)
## ファイル一覧
- stls/ : ケース用の STL ファイル
- stls/one_dial/ : 1ダイヤル版用
- stls/full/ : 9ダイヤル版用準備中
- pattern/ : エンコーダ用パーターンファイル
- board/ : KiCad用の回路図と基板のレイアウトデータ
- board/gerber/ : 基板製造用ガーバーデータと組み立て用の配置情報
- firmware/ : Raspberry Pi Pico C/C++ SDK向けファームウェア開発リソース一式
- firmware/prebuilt : コンパイル済みファームウェアバイナリ
## 組みたて
1ダイヤルのみ搭載し、組み立てやすくした、1ダイヤル版と、
9つのダイヤルを搭載した、フル版を用意しています。
### 1ダイヤル版
![1ダイヤル版](./images/one-dial.webp)
#### 必要な部品
- 3Dプリント品(各1つ)
- ケース下面(case_bottom.stl)
- ケース上面(case_top.stl)
- ダイヤル(dial.stl)
- ダイヤルキャップ(dial_cap.stl)
- エンコーダ(encoder.stl)
- クラッチ(clutch.stl)
- 固定用カバー(cover.stl 灰色など白に近い色で印刷)
- 三連ツメ(end3.stl)
- エンコーダパターン pattern/one_dial.svg
- ねじ類
- 直径2mm 長さ4mmのセルフタッピングスクリュー 12個
- ワッシャー2個
- 電子部品([詳細についてはboardディレクトリを参照のこと](./board/README_ja.md))
- [ファームウエア書き込み済み](./firmware/README_ja.md) Raspberry Pi Pico 1個
- 組み立て済センサー基板 2枚
- [ユニポーラー ステッピングモーター 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/): 1個
- [モータードライバーモジュール](https://www.aitendo.com/product/12366): 1個
- 配線材: 適宜
#### Step 1: エンコーダの作成
[パターンファイル](./pattern/one_dial.svg)をコピー用紙に実寸大で印刷する。
印刷面を下にし上からエンコーダ部品をのりで貼り、周辺と中央をカットする。
エンコーダ部品にある◆マークと紙にある▼マークを合わせる。
![マークをあわせる](./images/encoder-mark.webp)
![組みあげたエンコーダ](./images/encoder.webp)
#### Step 2: カバーモジュールの組み立て
カバーは、フォトセンサーを使う仕組み上、透過しない色で印刷する必要があります。
試作では灰色のPLAで印刷しました。
他の色の場合、センサ基板の抵抗値を調節する必要があるかもしれません。
裏面に、センサ基板2枚をネジ止めします。
同じく裏面にステッピングモータをネジ止めします。
モータに近い側の基板をセンサーL基板、もう1つの基板をセンサーH基板を呼びます。
![裏面](./images/cover-back.webp)
表面に出てるモータ軸にクラッチをつけます。
![表面](./images/cover-front.webp)
#### Step 3: ケースへの取りつけ
裏側から、エンコーダ、そしてカバーを取り付けます。
カバーの向きは下の写真を参考にしてください。
![組みつけ](./images/assemble.webp)
表から軸にダイヤルと、キャップをつけます。
ダイヤルと軸には小さいマークがあるので、マークをあわせます。
![ダイヤルのマーク](./images/mark-on-encoder.webp)
上からダイヤルキャップをつけネジで止めます。
![全体](./images/one-dial.webp)
#### Step 4: 配線
RaspberryPiPicoにセンサ基板とモータドライバをつけます。
![配線](./images/one-dial-connection.png)
[詳細についてはboardディレクトリを参照のこと](./board/README_ja.md)
基板はケース底面の端に両面テープなどでとめておきます。
### 9ダイヤル版
**9ダイヤル版は現在調整中です。**
![9ダイヤル版](./images/nine-dial.webp)
#### 必要な部品
- 3Dプリント品
- ケース下面 左右1つずつ
- ケース上面 左右1つずつ
- ダイヤル 9個 (エンターキーを含む)
- ダイヤルキャップ 9個
- エンコーダ 5種 計9個
- 6ビット用 1個
- 4ビット用 1個
- 3ビット用 3個
- 2ビット用 3個
- 1ビット用 1個
- クラッチ 9個
- 固定カバー 2種 計9個
- 小 7個
- 大 2個
- 回転しすぎ防止つめ 3種 9個
- 小 8個
- 3連 1個
- エンコーダパターン 9種類
- ねじ類
- 直径2mm 長さ4mmのセルフタッピングスクリュー 約100個
- ワッシャー 18個
- M3ボルトとナット 少々
- 電子部品([詳細については基板組み立てガイドを参照のこと](./board/README_ja.md))
- 組み立て[ファームウエア書き込み](./firmware/README_ja.md)済みメイン基板 1枚
- 組み立て済センサー基板 11枚
- [ユニポーラー ステッピングモーター 28BYJ-48 5V](https://akizukidenshi.com/catalog/g/g113256/): 9個
- 配線材: 適宜
#### Step 1: 左右の接続
ケース下面の左右をM3ねじ4本で接続します。
![左右接続](./images/connect-left-and-right.webp)
#### Step 2: エンコーダの作成
1ダイヤル版と同様です。
#### Step 2: カバーモジュールの組み立て
基本的には1ダイヤル版と同様です。
カバーのうち7つは、センサ基板を1枚だけ組みつけます。
2つは、センサ基板を2枚つけます。
#### Step 3: ケースへの取りつけ
基本的には1ダイヤル版と同様です。
#### Step 4: 配線
専用のメイン基板に、モータとセンサ基板を配線します。

2
mozc-dial/firmware/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.vscode
build

View File

@@ -0,0 +1,61 @@
# Firmware Development Guide
For the entire keyboard assembly, please refer to the [Build Guide](../buildguide.md).
## Directory List
- prebuilt/ : Pre-built firmware for each board
- common/ : Source files for libraries used in common by each board
- main/ : Source files and project for the 9-dial edition main chip
- sub/ : Source files and project for the 9-dial edition sub chip
- one_dial/ : Source files and project for the 1-dial edition Raspberry Pi Pico
## Pre-built Firmware
We have included pre-built firmware under `prebuilt/`, so you can use them as is if no changes are needed.
Use `main.uf2` and `sub.uf2` for the 9-dial edition, and `one_dial.uf2` for the 1-dial edition.
## Guide to Developing Your Own Firmware
Install [Visual Studio Code](https://code.visualstudio.com/) and add the [Raspberry Pi Pico extension](https://marketplace.visualstudio.com/items?itemName=raspberry-pi.raspberry-pi-pico).
From the `Raspberry Pi Pico Projects` extension in the Activity Bar, use `Import Project` to open each project's directory.
If you are using the extension for the first time, the development environment installation will begin. Once the project is imported, you can develop using `CMake` and `Run and Debug` in the Activity Bar.
As with the official Pico series boards, the 9-dial edition has `SWCLK` and `SWDIO` land pairs labeled `SWC/D` on the board for each chip. By preparing development equipment that supports SWD, such as the official Debug Probe, you can perform step execution in the editor.
### Standard Output
On the 9-dial edition, there are lands labeled `TX1` for the main chip and `TX2` for the sub-chip on the board. With the standard settings, the standard output is output from these pins as UART at a speed of 115200.
On the other hand, by changing `pico_enable_stdio_uart` to `0` and `pico_enable_stdio_semihosting` to `1` in `CMakeLists.txt`, you can switch the output to go through the debugger. This is convenient as you don't need to wire TX, but be aware that the operation will stop at the output point if debugger support is not enabled. It will also stop when the debugger is not attached, so it cannot be used for release builds. Debugger support can be enabled with `monitor arm semihosting enable`, but it is tedious to set it every time you start the debugger, so it is convenient to register it in each command setting in `.vscode/launch.json` as follows.
```
"postLaunchCommands": [
"monitor arm semihosting enable"
]
```
### Using PICO W or PICO 2 (W)
The 1-dial edition is set to use the official Raspberry Pi Pico board, but you can support other boards by changing the board setting from `pico` to `pico_w`, `pico2`, `pico2_w`, etc. in `set(PICO_BOARD pico CACHE STRING "Board type")` in `CMakeFiles.txt`. You should also be able to support 3rd party boards with `none`, etc.
### About EEPROM
Since the board setting for the 9-dial edition is `none`, a general-purpose driver for EEPROM will be included with a prescaler setting of 4. This should work with most EEPROMs. If you are using the reference parts, we have confirmed that the driver for W25Q080 works with a prescaler setting of 2, similar to the official board. By creating a `board_name.h` in `~/.pico-sdk/sdk/2.2.0/src/boards/include/boards/` and defining
```
#define PICO_BOOT_STAGE_2_CHOOSE_W25Q080 1
#define PICO_FLASH_SPI_CLKDIV 2
```
and specifying it in the board settings of `CMakeFiles.txt`, you can optimize EEPROM access via QSPI in case of a cache miss and draw out further performance.
### About Sensor Adjustment
By default, the sensor is pulled up by an internal resistor. If you want to adjust it by pulling up with an external resistor, change `gpio_pull_up(gpio);` to `gpio_disable_pulls(gpio);` in `PhotoSensor::PhotoSensor()` in `photo_sensor.cc`.
Alternatively, it is also possible to pull up with a combined resistance of the internal and external resistors while the internal resistor is enabled. In this case, the internal resistor has a nominal value of 50-80KΩ and is connected in parallel with the external resistor.
For details, please check the [Board Assembly Guide](../board/README.md).

View File

@@ -0,0 +1,61 @@
# ファームウェア開発ガイド
キーボードの組み立て全体については[ビルドガイド](../buildguide_ja.md)を参照してください。
## ディレクトリ一覧
- prebuilt/ : 各ボード向けに事前にビルドしたファームウェア
- common/ : 各ボード共通で用いられるライブラリのソースファイル
- main/ : 9ダイヤル版メインチップ用のソースファイルとプロジェクト
- sub/ : 9ダイヤル版サブチップ用のソースファイルとプロジェクト
- one_dial/ : 1ダイヤル版 Raspberry Pi Pico用のソーとファイルとプロジェクト
## 事前にビルドしたファームウェア
`prebuilt/`以下に事前にビルドしたファームウェアを収めていますので、変更が不要の場合はこれらをそのまま用いることができます。
9ダイヤル板は`main.uf2``sub.uf2`を、1ダイヤル板は`one_dial.uf2`を使用します。
## 独自のファームウェア開発の手引き
[Visual Studio Code](https://code.visualstudio.com/) をインストールし、[Raspberry Pi Pico拡張](https://marketplace.visualstudio.com/items?itemName=raspberry-pi.raspberry-pi-pico) を追加します。
Activity Barの`Raspberry Pi Pico Projects`の拡張機能から`Import Project`を使い、各プロジェクトのディレクトリを開いてください。
拡張をはじめて使う場合、開発環境のインストールが始まります。プロジェクトが取り込まれると、Activity Barの`CMake``Run and Debug`を使って開発できるようになります。
公式のPicoシリーズボードに搭載されているように、9ダイヤル板でも基板上に`SWC/D`と書かれた`SWCLK``SWDIO`のランドペアがそれぞれのチップごとに用意されています。公式のDebug Probe等のSWDをサポートした開発機材を用意する事で、エディタ上でステップ実効等が可能です。
### 標準出力について
9ダイヤル板では基板上にメインチップ用の`TX1`、サブチップ用の`TX2`と書かれたランドが用意されており、標準の設定では標準出力はこれらのピンから速度115200のUARTとして出力されています。
一方で`CMakeLists.txt``pico_enable_stdio_uart``0`に、`pico_enable_stdio_semihosting``1`に変更する事で、出力をデバッガ経由に切り替える事ができます。TXを配線せずに済むので便利ですが、デバッガのサポートを有効にしないと出力のところで動作停止するので注意が必要です。デバッガがアタッチされていない時も同様に停止するので、リリースビルドには使えません。デバッガのサポートは`monitor arm semihosting enable`で有効に出来ますが、デバッガ起動ごとに毎回設定するのは大変なので、`.vscode/launch.json`の各コマンド設定内に以下のように登録しておくと便利です。
```
"postLaunchCommands": [
"monitor arm semihosting enable"
]
```
### PICO WやPICO 2 (W)の利用
1ダイヤル板は公式のRaspberry Pi Picoボードを使うように設定してありますが、`CMakeFiles.txt`内の`set(PICO_BOARD pico CACHE STRING "Board type")`のところでボード設定を`pico`から`pico_w``pico2``pico2_w`等に変更する事で他のボードにも対応できるかと思います。3rd party製のボードについても`none`等で対応できるかと思います。
### EEPROMについて
9ダイヤル版はボード設定を`none`にしているので、EEPROMについては汎用のドライバが分周4で組み込まれます。これでだいたいのEEPROMで動作するはずです。リファレンスの部品を使っている場合は、公式ボードと同様にW25Q080用のドライバが分周2で動作する事を確認しています。`~/.pico-sdk/sdk/2.2.0/src/boards/include/boards/``ボード名.h`を作成し、その中で
```
#define PICO_BOOT_STAGE_2_CHOOSE_W25Q080 1
#define PICO_FLASH_SPI_CLKDIV 2
```
などを定義し、`CMakeFiles.txt`のボード設定で指定する事で、キャッシュミス時のQSPI経由でのEEPROMアクセスを最適化し、さらなる性能を引き出す事も可能です。
### センサーの調整について
標準ではセンサーは内部抵抗でpull-upされています。外部抵抗によりpull-upで調整したい場合、`photo_sensor.cc``PhotoSensor::PhotoSensor()`内にある`gpio_pull_up(gpio);``gpio_disable_pulls(gpio);`に変更してください。
あるいは、内部抵抗を有効にしたまま外部抵抗との合成抵抗でpull-upする事も可能です。この場合、内部抵抗は公称値で50-80KΩ、外部抵抗とは並列接続になります。
詳細は[基板組立ガイド](../board/README_ja.md)を確認してください。

View File

@@ -0,0 +1,45 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "dial_controller.h"
#include <algorithm>
namespace {
constexpr uint8_t kBasePosition = 0;
uint8_t ConvertGrayToBinary(uint8_t gray) {
uint8_t binary = gray;
uint8_t mask = gray >> 1;
while (mask) {
binary ^= mask;
mask >>= 1;
}
return binary;
}
} // namespace
DialController::DialController() {}
void DialController::Update(uint8_t sensor_gray_code) {
position_ = ConvertGrayToBinary(sensor_gray_code);
max_position_ = std::max(max_position_, position_);
if (position_ == kBasePosition && max_position_ != kBasePosition) {
decided_position_ = max_position_;
max_position_ = kBasePosition;
}
}
bool DialController::IsBasePosition() const {
return position_ == kBasePosition;
}
std::optional<uint8_t> DialController::PopDecidedPosition() {
auto result = decided_position_;
decided_position_ = std::nullopt;
return result;
}

View File

@@ -0,0 +1,28 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef COMMON_DIAL_CONTROLLER_H
#define COMMON_DIAL_CONTROLLER_H
#include <cstdint>
#include <optional>
class DialController {
public:
DialController();
DialController(const DialController&) = delete;
DialController& operator=(const DialController&) = delete;
~DialController() = default;
void Update(uint8_t sensor_gray_code);
bool IsBasePosition() const;
std::optional<uint8_t> PopDecidedPosition();
private:
uint8_t position_ = 0u;
uint8_t max_position_ = 0u;
std::optional<uint8_t> decided_position_;
};
#endif // COMMON_DIAL_CONTROLLER_H

View File

@@ -0,0 +1,108 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "motor_controller.h"
#include <cstdio>
#include <initializer_list>
#include "hardware/gpio.h"
#include "pico/time.h"
static constexpr int kMotorStepIntervalInUs = 2000;
// Activate 1 of 8 motor drivers in a specific step phase.
class MotorController::Decoder {
public:
Decoder(int8_t a0, int8_t a1, int8_t a2, int8_t en)
: a0_(a0), a1_(a1), a2_(a2), en_(en) {
for (int8_t gpio : {en, a0, a1, a2}) {
gpio_init(gpio);
gpio_set_dir(gpio, GPIO_OUT);
gpio_put(gpio, false);
}
}
Decoder(const Decoder&) = delete;
Decoder& operator=(const Decoder&) = delete;
~Decoder() = default;
void Select(uint8_t index) {
hard_assert(index < 8);
gpio_put(a0_, index & 1);
gpio_put(a1_, index & 2);
gpio_put(a2_, index & 4);
gpio_put(en_, true);
}
void Unselect() { gpio_put(en_, false); }
private:
int8_t a0_;
int8_t a1_;
int8_t a2_;
int8_t en_;
};
// static
bool MotorController::OnAlarm(repeating_timer* t) {
static_cast<MotorController*>(t->user_data)->Step();
return true;
}
MotorController::MotorController(Mode mode) {
if (mode == Mode::k9Motor) {
// Each decoder is responsible for a specific phase to driver 1 of 8 motor
// drivers. As we have 4 phases, it can drive 4 motors at the same time, and
// by time division multiplexing, we make them drive 8 motors.
decoder_[0] = std::make_unique<Decoder>(1, 2, 3, 4);
decoder_[1] = std::make_unique<Decoder>(5, 6, 7, 8);
decoder_[2] = std::make_unique<Decoder>(9, 10, 11, 12);
decoder_[3] = std::make_unique<Decoder>(13, 14, 15, 16);
}
// 9th motor driver is controlled via GPIOs directly.
for (int8_t gpio : {17, 18, 19, 20}) {
gpio_init(gpio);
gpio_set_dir(gpio, GPIO_OUT);
gpio_put(gpio, false);
}
add_repeating_timer_us(-kMotorStepIntervalInUs, OnAlarm, this, &timer_);
}
MotorController::~MotorController() {}
void MotorController::Start(uint8_t index) {
hard_assert(index < kNumOfMotors);
started_[index] = true;
}
void MotorController::Stop(uint8_t index) {
hard_assert(index < kNumOfMotors);
started_[index] = false;
}
void MotorController::Step() {
// Drive first 4 motor drivers in even phases, and later 4 ones in odd
// phases.
size_t start_index = (phase_ & 1) ? kNumOfPhases : 0;
size_t half_phase = phase_ >> 1;
for (size_t i = start_index; i < start_index + kNumOfPhases; ++i) {
if (!decoder_[i]) {
continue;
}
if (started_[i]) {
decoder_[(i + half_phase) % kNumOfPhases]->Select(i);
} else {
decoder_[(i + half_phase) % kNumOfPhases]->Unselect();
}
}
// Drive the last 9th motor drivers.
bool on = started_[8] && phase_ & 1;
gpio_put(17, on && half_phase == 0);
gpio_put(18, on && half_phase == 1);
gpio_put(19, on && half_phase == 2);
gpio_put(20, on && half_phase == 3);
phase_ = (phase_ + 7) % 8;
}

View File

@@ -0,0 +1,38 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef COMMON_MOTOR_CONTROLLER_H_
#define COMMON_MOTOR_CONTROLLER_H_
#include <cstdint>
#include <memory>
#include "pico/time.h"
class MotorController final {
public:
enum class Mode { k1Motor, k9Motor };
explicit MotorController(Mode = Mode::k9Motor);
MotorController(const MotorController&) = delete;
MotorController& operator=(const MotorController&) = delete;
~MotorController();
void Start(uint8_t index);
void Stop(uint8_t index);
private:
class Decoder;
static bool OnAlarm(repeating_timer* t);
void Step();
static constexpr size_t kNumOfPhases = 4;
static constexpr size_t kNumOfMotors = 9;
std::unique_ptr<Decoder> decoder_[kNumOfPhases];
bool started_[kNumOfMotors] = { false };
uint8_t phase_ = 0;
repeating_timer timer_;
};
#endif // COMMON_MOTOR_CONTROLLER_H_

View File

@@ -0,0 +1,32 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "photo_sensor.h"
#include "hardware/gpio.h"
PhotoSensor::PhotoSensor(int8_t bit0,
int8_t bit1,
int8_t bit2,
int8_t bit3,
int8_t bit4,
int8_t bit5)
: bits({bit0, bit1, bit2, bit3, bit4, bit5}) {
for (int8_t gpio : bits) {
gpio_init(gpio);
gpio_set_dir(gpio, GPIO_IN);
gpio_pull_up(gpio); // Built-in 50-80K pull-up
}
}
uint8_t PhotoSensor::Read() {
uint8_t value = 0;
for (size_t bit = 0; bit < bits.size(); ++bit) {
if (bits[bit] == kNotUsed) {
break;
}
value |= gpio_get(bits[bit]) ? (1 << bit) : 0;
}
return value;
}

View File

@@ -0,0 +1,33 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef COMMON_PHOTO_SENSOR_H_
#define COMMON_PHOTO_SENSOR_H_
#include <array>
#include <cstddef>
#include <cstdint>
class PhotoSensor final {
public:
PhotoSensor(int8_t bit0,
int8_t bit1 = kNotUsed,
int8_t bit2 = kNotUsed,
int8_t bit3 = kNotUsed,
int8_t bit4 = kNotUsed,
int8_t bit5 = kNotUsed);
PhotoSensor(const PhotoSensor&) = delete;
PhotoSensor& operator=(const PhotoSensor&) = delete;
~PhotoSensor() = default;
uint8_t Read();
private:
static constexpr int8_t kNotUsed = -1;
static constexpr size_t kMaxWidth = 6;
std::array<int8_t, kMaxWidth> bits;
};
#endif // COMMON_PHOTO_SENSOR_H_

View File

@@ -0,0 +1,145 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "usage_tables.h"
namespace usage_tables {
const std::vector<uint8_t> a = {
0x00, // no input (blank area between the base and the first position)
0x2f, // @
0x34, // :
0x87, // _
0x13, // p
0x33, // ;
0x38, // /
0x12, // o
0x0f, // l
0x37, // .
0x0c, // i
0x0e, // k
0x36, // ,
0x18, // u
0x0d, // j
0x10, // m
0x1c, // y
0x0b, // h
0x11, // n
0x17, // t
0x0a, // g
0x05, // b
0x15, // r
0x09, // f
0x19, // v
0x08, // e
0x07, // d
0x06, // c
0x1a, // w
0x16, // s
0x1b, // x
0x14, // q
0x04, // a
0x1d, // z
0x39, // <CAPS>
0x2c, // <SPACE>
};
const std::vector<uint8_t> fn_a = {};
const std::vector<uint8_t> modifier_a = {};
const std::vector<uint8_t> b = {
0x2e, // ^ (~)
0x29, // <ESC>
0x2b, // <TAB>
};
const std::vector<uint8_t> fn_b = {};
const std::vector<uint8_t> modifier_b = {};
const std::vector<uint8_t> c = {
0x00, // <SHIFT>
0x00, // <CTRL>
0x00, // <ALT>
0x00, // <FN>
};
const std::vector<uint8_t> fn_c = {};
const std::vector<uint8_t> modifier_c = {
kModifierLeftShift,
kModifierLeftCtrl,
kModifierLeftAlt,
kModifierLeftGUI,
};
const std::vector<uint8_t> d = {
0x00, // no input
0x28, // <ENTER>
};
const std::vector<uint8_t> fn_d = {};
const std::vector<uint8_t> modifier_d = {};
const std::vector<uint8_t> e = {
0x00, // no input
0x4d, // <END>
0x4e, // <PAGE DOWN>
0x4b, // <PAGE UP>
0x4a, // <HOME>
0x49, // <INS>
0x4c, // <DEL>
};
const std::vector<uint8_t> fn_e = {};
const std::vector<uint8_t> modifier_e = {};
const std::vector<uint8_t> f = {
0x00, // no input
0x4f, // <RIGHT>
0x52, // <UP>
0x50, // <LEFT>
0x51, // <DOWN>
};
const std::vector<uint8_t> fn_f = {};
const std::vector<uint8_t> modifier_f = {};
const std::vector<uint8_t> g = {
0x55, // KP-*
0x56, // KP-/
0x63, // KP-.
};
const std::vector<uint8_t> fn_g = {};
const std::vector<uint8_t> modifier_g = {};
const std::vector<uint8_t> h = {
0x57, // KP-+
0x56, // KP--
0x67, // KP-=
};
const std::vector<uint8_t> fn_h = {};
const std::vector<uint8_t> modifier_h = {};
const std::vector<uint8_t> i = {
0x00, // no input
0x26, // 9
0x25, // 8
0x24, // 7
0x23, // 6
0x22, // 5
0x21, // 4
0x20, // 3
0x1f, // 2
0x1e, // 1
0x27, // 0
};
const std::vector<uint8_t> fn_i = {
0x00, // no input
0x42, // F9
0x41, // F8
0x40, // F7
0x3f, // F6
0x3e, // F5
0x3d, // F4
0x3c, // F3
0x3b, // F2
0x3a, // F1
0x43, // F10
};
const std::vector<uint8_t> modifier_i = {};
} // namespace usage_tables

View File

@@ -0,0 +1,54 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef COMMON_USAGE_TABLES_
#define COMMON_USAGE_TABLES_
#include <vector>
#include <cstdint>
namespace usage_tables {
static constexpr uint8_t kModifierLeftCtrl = (1 << 0);
static constexpr uint8_t kModifierLeftShift = (1 << 1);
static constexpr uint8_t kModifierLeftAlt = (1 << 2);
static constexpr uint8_t kModifierLeftGUI = (1 << 3);
static constexpr uint8_t kModifierRightCtrl = (1 << 4);
static constexpr uint8_t kModifierRightShift = (1 << 5);
static constexpr uint8_t kModifierRightAlt = (1 << 6);
static constexpr uint8_t kModifierRightGUI = (1 << 7);
extern const std::vector<uint8_t> a;
extern const std::vector<uint8_t> b;
extern const std::vector<uint8_t> c;
extern const std::vector<uint8_t> d;
extern const std::vector<uint8_t> e;
extern const std::vector<uint8_t> f;
extern const std::vector<uint8_t> g;
extern const std::vector<uint8_t> h;
extern const std::vector<uint8_t> i;
extern const std::vector<uint8_t> fn_a;
extern const std::vector<uint8_t> fn_b;
extern const std::vector<uint8_t> fn_c;
extern const std::vector<uint8_t> fn_d;
extern const std::vector<uint8_t> fn_e;
extern const std::vector<uint8_t> fn_f;
extern const std::vector<uint8_t> fn_g;
extern const std::vector<uint8_t> fn_h;
extern const std::vector<uint8_t> fn_i;
extern const std::vector<uint8_t> modifier_a;
extern const std::vector<uint8_t> modifier_b;
extern const std::vector<uint8_t> modifier_c;
extern const std::vector<uint8_t> modifier_d;
extern const std::vector<uint8_t> modifier_e;
extern const std::vector<uint8_t> modifier_f;
extern const std::vector<uint8_t> modifier_g;
extern const std::vector<uint8_t> modifier_h;
extern const std::vector<uint8_t> modifier_i;
} // namespace usage_tables
#endif // COMMON_USAGE_TABLES_

View File

@@ -0,0 +1,353 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "usb_device.h"
#include <cstdio>
#include <cstring>
#include <memory>
#include "hardware/irq.h"
#include "hardware/regs/usb.h"
#include "hardware/resets.h"
#include "hardware/structs/usb.h"
#include "pico/types.h"
namespace {
constexpr uint8_t kControlEndpoint = 0u;
UsbDevice* sUsbDevice = nullptr;
} // namespace
extern "C" void isr_usbctrl(void) {
UsbDevice::HandleInterrupt();
}
// static
void UsbDevice::HandleInterrupt() {
uint32_t status = usb_hw->ints;
if (status & USB_INTS_SETUP_REQ_BITS) {
static_cast<usb_hw_t*>(hw_clear_alias_untyped(usb_hw))->sie_status =
USB_SIE_STATUS_SETUP_REC_BITS;
sUsbDevice->HandleSetupRequest();
}
if (status & USB_INTS_BUFF_STATUS_BITS) {
sUsbDevice->HandleBufferStatus();
}
if (status & USB_INTS_BUS_RESET_BITS) {
static_cast<usb_hw_t*>(hw_clear_alias_untyped(usb_hw))->sie_status =
USB_SIE_STATUS_BUS_RESET_BITS;
sUsbDevice->HandleBusReset();
}
}
UsbDevice::UsbDevice(
const DeviceDescriptor& device_descriptor,
const ConfigurationDescriptor& configuration_descriptor,
const std::vector<InterfaceDescriptor>& interface_descriptors,
const std::vector<EndPointDescriptor>& endpoint_descriptors,
const std::vector<std::string>& strings)
: device_descriptor_(device_descriptor),
configuration_descriptor_(configuration_descriptor),
interface_descriptors_(interface_descriptors),
endpoint_descriptors_(endpoint_descriptors),
strings_(strings) {
hard_assert(sUsbDevice == nullptr);
sUsbDevice = this;
reset_unreset_block_num_wait_blocking(RESET_USBCTRL);
memset(usb_dpram, 0, sizeof(*usb_dpram));
irq_set_enabled(USBCTRL_IRQ, true);
usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS;
usb_hw->pwr =
USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS;
usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS;
usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS;
usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS |
USB_INTS_SETUP_REQ_BITS;
sending_data_.push_back(std::span<const uint8_t>());
receiving_data_.push_back(std::span<uint8_t>());
uint32_t dpram_offset = offsetof(usb_device_dpram_t, epx_data);
for (size_t i = 0; i < endpoint_descriptors_.size(); ++i) {
uint32_t control =
EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER |
(endpoint_descriptors_[i].bmAttributes << EP_CTRL_BUFFER_TYPE_LSB) |
dpram_offset;
if (endpoint_descriptors_[i].bEndpointAddress & kDirIn) {
usb_dpram->ep_ctrl[i].in = control;
} else {
usb_dpram->ep_ctrl[i].out = control;
}
dpram_offset += endpoint_descriptors_[i].wMaxPacketSize;
sending_data_.push_back(std::span<const uint8_t>());
receiving_data_.push_back(std::span<uint8_t>());
}
size_t endpoints = sending_data_.size();
in_next_pid_.resize(endpoints);
out_next_pid_.resize(endpoints);
static_cast<usb_hw_t*>(hw_set_alias_untyped(usb_hw))->sie_ctrl =
USB_SIE_CTRL_PULLUP_EN_BITS;
}
void UsbDevice::FillConfigurations(std::vector<uint8_t>& buffer) {
buffer.clear();
buffer.reserve(configuration_descriptor_.wTotalLength);
std::span<const uint8_t> config_span(
reinterpret_cast<const uint8_t*>(&configuration_descriptor_),
sizeof(configuration_descriptor_));
buffer.insert(buffer.end(), config_span.begin(), config_span.end());
for (const auto& interface_descriptor : interface_descriptors_) {
std::span<const uint8_t> interface_span(
reinterpret_cast<const uint8_t*>(&interface_descriptor),
sizeof(interface_descriptor));
buffer.insert(buffer.end(), interface_span.begin(), interface_span.end());
}
for (const auto& endpoint_descriptor : endpoint_descriptors_) {
std::span<const uint8_t> endpoint_span(
reinterpret_cast<const uint8_t*>(&endpoint_descriptor),
sizeof(endpoint_descriptor));
buffer.insert(buffer.end(), endpoint_span.begin(), endpoint_span.end());
}
}
void UsbDevice::GetDescriptor(volatile SetupPacket* setup) {
uint8_t type = setup->wValue >> 8;
switch (type) {
case kDescriptorTypeDevice:
Send(kControlEndpoint, std::span<const uint8_t>(
reinterpret_cast<const uint8_t*>(&device_descriptor_),
std::min(sizeof(device_descriptor_),
static_cast<size_t>(setup->wLength))));
break;
case kDescriptorTypeConfiguration: {
FillConfigurations(setup_buffer_);
Send(kControlEndpoint, std::span<const uint8_t>(
setup_buffer_.data(),
std::min(setup_buffer_.size(),
static_cast<size_t>(setup->wLength))));
break;
}
case kDescriptorTypeString: {
uint8_t index = setup->wValue & 0xff;
setup_buffer_.clear();
if (index == 0) {
setup_buffer_.reserve(4);
setup_buffer_.push_back(0x04);
setup_buffer_.push_back(kDescriptorTypeString);
setup_buffer_.push_back(0x09);
setup_buffer_.push_back(0x04);
} else if (index <= strings_.size()) {
size_t size = strings_[index - 1].size() * 2 + 2;
setup_buffer_.reserve(size);
setup_buffer_.push_back(size);
setup_buffer_.push_back(kDescriptorTypeString);
for (size_t i = 0; i < strings_[index - 1].size(); ++i) {
setup_buffer_.push_back(strings_[index - 1][i]);
setup_buffer_.push_back(0);
}
} else {
setup_buffer_.reserve(2);
setup_buffer_.push_back(0x02);
setup_buffer_.push_back(kDescriptorTypeString);
}
Send(kControlEndpoint, std::span<const uint8_t>(
setup_buffer_.data(),
std::min(setup_buffer_.size(),
static_cast<size_t>(setup->wLength))));
break;
}
default:
printf("unsupported get descriptor: $%02x\n", type);
break;
}
}
// Note: better to return STALL on unsupported requests.
void UsbDevice::HandleSetupRequest(volatile SetupPacket* setup) {
uint8_t type =
setup->bmRequestType & ~(kRecipientInterface | kRecipientEndPoint);
if (type == kDirOut) {
switch (setup->bRequest) {
case kRequestClearFeature:
AcknowledgeOutRequest();
break;
case kRequestSetAddress:
SetAddress(setup);
break;
case kRequestSetConfiguration:
SetConfiguration(setup);
break;
default:
printf("unsupported setup out: $%02x\n", setup->bRequest);
break;
}
} else if (type == kDirIn) {
switch (setup->bRequest) {
case kRequestGetDescriptor:
GetDescriptor(setup);
break;
default:
printf("unsupported setup in: $%02x\n", setup->bRequest);
break;
}
}
}
void UsbDevice::AcknowledgeOutRequest() {
Send(kControlEndpoint, std::span<uint8_t>({}));
}
void UsbDevice::Send(uint8_t endpoint, std::span<const uint8_t> data) {
if (endpoint == kControlEndpoint) {
in_next_pid_[endpoint] = 1u;
}
sending_data_[endpoint] = data;
SendInternal(endpoint);
}
void UsbDevice::Receive(uint8_t endpoint, std::span<uint8_t> data) {
receiving_data_[endpoint] = data;
out_next_pid_[endpoint] = 1u;
ReceiveInternal(endpoint);
}
void UsbDevice::SendInternal(uint8_t endpoint) {
size_t transfer_size = std::min(sending_data_[endpoint].size(), GetMaxPacketSize(endpoint));
uint8_t* buffer = GetTransferBuffer(endpoint);
memcpy(buffer, sending_data_[endpoint].data(), transfer_size);
uint32_t pid =
in_next_pid_[endpoint] ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID;
in_next_pid_[endpoint] ^= 1u;
usb_dpram->ep_buf_ctrl[endpoint].in =
transfer_size | USB_BUF_CTRL_AVAIL | USB_BUF_CTRL_FULL | pid;
size_t remaining_size = sending_data_[endpoint].size() - transfer_size;
if (remaining_size) {
sending_data_[endpoint] =
std::span(&sending_data_[endpoint][transfer_size], remaining_size);
} else {
sending_data_[endpoint] = std::span<const uint8_t>();
}
}
void UsbDevice::ReceiveInternal(uint8_t endpoint) {
size_t transfer_size = std::min(receiving_data_[endpoint].size(), GetMaxPacketSize(endpoint));
uint32_t pid =
out_next_pid_[endpoint] ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID;
usb_dpram->ep_buf_ctrl[endpoint].out =
transfer_size | USB_BUF_CTRL_AVAIL | pid;
}
void UsbDevice::HandleSetupRequest() {
volatile SetupPacket* setup =
reinterpret_cast<volatile SetupPacket*>(&usb_dpram->setup_packet);
HandleSetupRequest(setup);
}
void UsbDevice::HandleBufferStatus() {
uint32_t status = usb_hw->buf_status;
uint8_t endpoint_bits = (1 + endpoint_descriptors_.size()) * 2;
for (uint8_t i = 0; i < endpoint_bits; ++i) {
uint32_t bit = 1 << i;
uint8_t endpoint = i >> 1;
bool out = i & 1;
if ((status & bit) == 0) {
continue;
}
static_cast<usb_hw_t*>(hw_clear_alias_untyped(usb_hw))->buf_status = bit;
if (out) {
OnReceived(endpoint,
usb_dpram->ep_buf_ctrl[endpoint].out & USB_BUF_CTRL_LEN_MASK);
} else {
OnSent(endpoint,
usb_dpram->ep_buf_ctrl[endpoint].in & USB_BUF_CTRL_LEN_MASK);
}
}
}
void UsbDevice::HandleBusReset() {
address_ = 0;
ready_ = false;
usb_hw->dev_addr_ctrl = 0;
should_set_address_ = false;
for (auto& data : sending_data_) {
data = std::span<const uint8_t>();
}
for (auto& data : receiving_data_) {
data = std::span<uint8_t>();
}
}
void UsbDevice::OnSent(uint8_t endpoint, uint32_t length) {
if (should_set_address_) {
usb_hw->dev_addr_ctrl = address_;
should_set_address_ = false;
} else if (length == 0) {
return;
} else if (sending_data_[endpoint].empty()) {
Receive(endpoint, {});
OnCompleteToSend(endpoint);
} else {
SendInternal(endpoint);
}
}
void UsbDevice::OnReceived(uint8_t endpoint, uint32_t length) {
if (length == 0) {
return;
}
size_t receive_size =
std::min(receiving_data_[endpoint].size(), static_cast<size_t>(length));
uint8_t* buffer = GetTransferBuffer(endpoint);
memcpy(receiving_data_[endpoint].data(), buffer, receive_size);
size_t remaining_size = receiving_data_[endpoint].size() - receive_size;
if (remaining_size) {
receiving_data_[endpoint] =
std::span(&receiving_data_[endpoint][receive_size], remaining_size);
ReceiveInternal(endpoint);
} else {
receiving_data_[endpoint] = std::span<uint8_t>();
Send(endpoint, {});
}
}
void UsbDevice::SetAddress(volatile SetupPacket* setup) {
address_ = setup->wValue & 0xff;
should_set_address_ = true;
AcknowledgeOutRequest();
}
void UsbDevice::SetConfiguration(volatile SetupPacket* setup) {
ready_ = true;
AcknowledgeOutRequest();
}
size_t UsbDevice::GetMaxPacketSize(uint8_t endpoint) {
if (endpoint == kControlEndpoint) {
return device_descriptor_.bMaxPacketSize0;
} else if (endpoint <= endpoint_descriptors_.size()) {
return endpoint_descriptors_[endpoint - 1].wMaxPacketSize;
}
hard_assert(false);
return 0;
}
uint8_t* UsbDevice::GetTransferBuffer(uint8_t endpoint) {
if (endpoint == kControlEndpoint) {
return usb_dpram->ep0_buf_a;
}
uint8_t* buffer = usb_dpram->epx_data;
for (size_t i = 1; i < endpoint; ++i) {
buffer += endpoint_descriptors_[i].wMaxPacketSize;
}
return buffer;
}

View File

@@ -0,0 +1,145 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef COMMON_USB_DEVICE_H_
#define COMMON_USB_DEVICE_H_
#include <cstdint>
#include <span>
#include <string>
#include <vector>
class UsbDevice {
public:
struct SetupPacket {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} __attribute__((packed));
struct DeviceDescriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} __attribute__((packed));
struct ConfigurationDescriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wTotalLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
} __attribute__((packed));
struct InterfaceDescriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} __attribute__((packed));
struct EndPointDescriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} __attribute__((packed));
static constexpr uint8_t kDirOut = 0x00u;
static constexpr uint8_t kDirIn = 0x80u;
static constexpr uint8_t kTypeClass = 0x20;
static constexpr uint8_t kRecipientInterface = 0x01;
static constexpr uint8_t kRecipientEndPoint = 0x02;
static constexpr uint8_t kRequestClearFeature = 0x01u;
static constexpr uint8_t kRequestSetAddress = 0x05u;
static constexpr uint8_t kRequestGetDescriptor = 0x06u;
static constexpr uint8_t kRequestSetConfiguration = 0x09u;
static constexpr uint8_t kDescriptorTypeDevice = 0x01u;
static constexpr uint8_t kDescriptorTypeConfiguration = 0x02u;
static constexpr uint8_t kDescriptorTypeString = 0x03u;
static constexpr uint8_t kDescriptorTypeInterface = 0x04u;
static constexpr uint8_t kDescriptorTypeEndPoint = 0x05u;
static constexpr uint8_t kEndPointAttributeInterrupt = 0x03u;
static void HandleInterrupt();
UsbDevice(const DeviceDescriptor& device_descriptor,
const ConfigurationDescriptor& configuration_descriptor,
const std::vector<InterfaceDescriptor>& interface_descriptors,
const std::vector<EndPointDescriptor>& endpoint_descriptors,
const std::vector<std::string>& strings);
UsbDevice(const UsbDevice&) = delete;
UsbDevice& operator=(const UsbDevice&) = delete;
virtual ~UsbDevice() = default;
protected:
virtual void FillConfigurations(std::vector<uint8_t>& buffer);
virtual void GetDescriptor(volatile SetupPacket* setup);
virtual void HandleSetupRequest(volatile SetupPacket* setup);
virtual void OnCompleteToSend(uint8_t endpoint) {};
void AcknowledgeOutRequest();
void Send(uint8_t endpoint, std::span<const uint8_t> data);
void Receive(uint8_t endpoint, std::span<uint8_t> data);
private:
void HandleSetupRequest();
void HandleBufferStatus();
void HandleBusReset();
void SendInternal(uint8_t endpoint);
void ReceiveInternal(uint8_t endpoint);
void OnSent(uint8_t endpoint, uint32_t length);
void OnReceived(uint8_t endpoint, uint32_t length);
void SetAddress(volatile SetupPacket*);
void SetConfiguration(volatile SetupPacket*);
size_t GetMaxPacketSize(uint8_t endpoint);
uint8_t* GetTransferBuffer(uint8_t endpoint);
const DeviceDescriptor& device_descriptor_;
const ConfigurationDescriptor& configuration_descriptor_;
const std::vector<InterfaceDescriptor>& interface_descriptors_;
const std::vector<EndPointDescriptor>& endpoint_descriptors_;
const std::vector<std::string>& strings_;
uint8_t address_ = 0;
bool should_set_address_ = false;
bool ready_ = false;
std::vector<uint8_t> in_next_pid_ = {0u};
std::vector<uint8_t> out_next_pid_ = {0u};
std::vector<uint8_t> setup_buffer_;
std::vector<std::span<const uint8_t>> sending_data_;
std::vector<std::span<uint8_t>> receiving_data_;
};
#endif // COMMON_USB_DEVICE_H_

View File

@@ -0,0 +1,94 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "usb_hid_device.h"
UsbHidDevice::UsbHidDevice(
const DeviceDescriptor& device_descriptor,
const ConfigurationDescriptor& configuration_descriptor,
const std::vector<InterfaceDescriptor>& interface_descriptors,
const std::vector<EndPointDescriptor>& endpoint_descriptors,
const HidDescriptor& hid_descriptor,
const std::vector<uint8_t>& report_descriptor,
const std::vector<std::string>& strings)
: UsbDevice(device_descriptor,
configuration_descriptor,
interface_descriptors,
endpoint_descriptors,
strings),
configuration_descriptor_(configuration_descriptor),
interface_descriptors_(interface_descriptors),
endpoint_descriptors_(endpoint_descriptors),
hid_descriptor_(hid_descriptor),
report_descriptor_(report_descriptor) {}
void UsbHidDevice::Report(uint8_t endpoint, std::span<const uint8_t> report) {
Send(endpoint, report);
}
void UsbHidDevice::FillConfigurations(std::vector<uint8_t>& buffer) {
buffer.clear();
buffer.reserve(configuration_descriptor_.wTotalLength);
std::span<const uint8_t> config_span(
reinterpret_cast<const uint8_t*>(&configuration_descriptor_),
sizeof(configuration_descriptor_));
buffer.insert(buffer.end(), config_span.begin(), config_span.end());
for (const auto& interface_descriptor : interface_descriptors_) {
std::span<const uint8_t> interface_span(
reinterpret_cast<const uint8_t*>(&interface_descriptor),
sizeof(interface_descriptor));
buffer.insert(buffer.end(), interface_span.begin(), interface_span.end());
}
std::span<const uint8_t> hid_span(
reinterpret_cast<const uint8_t*>(&hid_descriptor_),
sizeof(hid_descriptor_));
buffer.insert(buffer.end(), hid_span.begin(), hid_span.end());
for (const auto& endpoint_descriptor : endpoint_descriptors_) {
std::span<const uint8_t> endpoint_span(
reinterpret_cast<const uint8_t*>(&endpoint_descriptor),
sizeof(endpoint_descriptor));
buffer.insert(buffer.end(), endpoint_span.begin(), endpoint_span.end());
}
}
void UsbHidDevice::GetDescriptor(volatile SetupPacket* setup) {
uint8_t type = setup->wValue >> 8;
switch (type) {
case UsbHidDevice::kDescriptorTypeReport:
Send(0, std::span<const uint8_t>(report_descriptor_));
break;
default:
UsbDevice::GetDescriptor(setup);
break;
}
}
void UsbHidDevice::HandleSetupRequest(volatile SetupPacket* setup) {
uint8_t type =
setup->bmRequestType & ~(kRecipientInterface | kRecipientEndPoint);
if (type == (kDirOut | kTypeClass)) {
switch (setup->bRequest) {
case kHidRequestSetReport:
receive_buffer_.resize(setup->wLength);
Receive(0, receive_buffer_);
break;
case kHidRequestSetIdle:
AcknowledgeOutRequest();
break;
case kHidRequestSetProtocol:
printf("set protocol: %s\n", setup->wValue ? "report" : "boot");
AcknowledgeOutRequest();
break;
default:
AcknowledgeOutRequest();
printf("unsupported HID class setup out: $%02x\n", setup->bRequest);
break;
}
} else if (type == (kDirIn | kTypeClass)) {
AcknowledgeOutRequest();
printf("unsupported HID class setup in: $%02x\n", setup->bRequest);
} else {
UsbDevice::HandleSetupRequest(setup);
}
}

View File

@@ -0,0 +1,60 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef COMMON_USB_HID_DEVICE_H_
#define COMMON_USB_HID_DEVICE_H_
#include "usb_device.h"
#include <cstdint>
#include <string>
#include <vector>
class UsbHidDevice : public UsbDevice {
public:
struct HidDescriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdHID;
uint8_t bCountryCode;
uint8_t bNumDescriptors;
uint8_t bReportDescriptorType;
uint16_t wDescriptorLength;
} __attribute__((packed));
static constexpr uint8_t kHidRequestSetReport = 0x09u;
static constexpr uint8_t kHidRequestSetIdle = 0x0au;
static constexpr uint8_t kHidRequestSetProtocol = 0x0bu;
static constexpr uint8_t kDescriptorTypeHid = 0x21u;
static constexpr uint8_t kDescriptorTypeReport = 0x22u;
UsbHidDevice(const DeviceDescriptor& device_descriptor,
const ConfigurationDescriptor& configuration_descriptor,
const std::vector<InterfaceDescriptor>& interface_descriptors,
const std::vector<EndPointDescriptor>& endpoint_descriptors,
const HidDescriptor& hid_descriptor,
const std::vector<uint8_t>& report_descriptor,
const std::vector<std::string>& strings);
UsbHidDevice(const UsbHidDevice&) = delete;
UsbHidDevice& operator=(const UsbHidDevice&) = delete;
~UsbHidDevice() override = default;
void Report(uint8_t endpoint, std::span<const uint8_t> report);
private:
// Implement UsbDevice.
void FillConfigurations(std::vector<uint8_t>& buffer) override;
void GetDescriptor(volatile SetupPacket* setup_packet) override;
void HandleSetupRequest(volatile SetupPacket* setup_packet) override;
const ConfigurationDescriptor& configuration_descriptor_;
const std::vector<InterfaceDescriptor>& interface_descriptors_;
const std::vector<EndPointDescriptor>& endpoint_descriptors_;
const HidDescriptor& hid_descriptor_;
const std::vector<uint8_t>& report_descriptor_;
std::vector<uint8_t> receive_buffer_;
};
#endif // COMMON_USB_HID_DEVICE_H_

View File

@@ -0,0 +1,203 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "usb_hid_keyboard.h"
namespace {
constexpr uint8_t kManufacturerStringIndex = 1;
constexpr uint8_t kProductStringIndex = 2;
constexpr uint8_t kSerialNumberStringIndex = 3;
constexpr uint8_t kModifierReportIndex = 0;
constexpr uint8_t kKeycodeReportIndex = 2;
UsbDevice::DeviceDescriptor device_descriptor = {
.bLength = sizeof(UsbDevice::DeviceDescriptor),
.bDescriptorType = UsbDevice::kDescriptorTypeDevice,
.bcdUSB = 0x0110,
.bDeviceClass = 0,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
.idVendor = 0,
.idProduct = 0,
.bcdDevice = 0,
.iManufacturer = kManufacturerStringIndex,
.iProduct = kProductStringIndex,
.iSerialNumber = kSerialNumberStringIndex,
.bNumConfigurations = 1};
std::vector<UsbDevice::EndPointDescriptor> endpoint_descriptors = {
{.bLength = sizeof(UsbDevice::EndPointDescriptor),
.bDescriptorType = UsbDevice::kDescriptorTypeEndPoint,
.bEndpointAddress = UsbDevice::kDirIn | 1,
.bmAttributes = UsbDevice::kEndPointAttributeInterrupt,
.wMaxPacketSize = 64,
.bInterval = 10},
{.bLength = sizeof(UsbDevice::EndPointDescriptor),
.bDescriptorType = UsbDevice::kDescriptorTypeEndPoint,
.bEndpointAddress = UsbDevice::kDirOut | 1,
.bmAttributes = UsbDevice::kEndPointAttributeInterrupt,
.wMaxPacketSize = 64,
.bInterval = 10}};
std::vector<UsbDevice::InterfaceDescriptor> interface_descriptors = {
{.bLength = sizeof(UsbDevice::InterfaceDescriptor),
.bDescriptorType = UsbDevice::kDescriptorTypeInterface,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = static_cast<uint8_t>(endpoint_descriptors.size()),
.bInterfaceClass = 3, // HID
.bInterfaceSubClass = 1, // Boot
.bInterfaceProtocol = 1, // Keyboard
.iInterface = 0}};
std::vector<uint8_t> report_descriptor = {
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
0x09, 0x06, /* USAGE (Keyboard) */
0xa1, 0x01, /* COLLECTION (Application) */
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
0x19, 0xe0, /* USAGE_MINIMUM (224) */
0x29, 0xe7, /* USAGE_MAXIMUM (231) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
0x75, 0x01, /* REPORT_SIZE (1) */
0x95, 0x08, /* REPORT_COUNT (8) */
0x81, 0x02, /* INPUT (Data,Var,Abs); Modifier byte */
0x75, 0x08, /* REPORT_SIZE (8) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x81, 0x01, /* INPUT (Constant); Reserved byte */
0x75, 0x01, /* REPORT_SIZE (1) */
0x95, 0x05, /* REPORT_COUNT (5) */
0x05, 0x08, /* USAGE_PAGE (LEDs) */
0x19, 0x01, /* USAGE_MINIMUM (1) */
0x29, 0x05, /* USAGE_MAXIMUM (5) */
0x91, 0x02, /* OUTPUT (Data,Var,Abs); LED report */
0x75, 0x03, /* REPORT_SIZE (3) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x91, 0x01, /* OUTPUT (Constant); LED report padding */
0x75, 0x08, /* REPORT_SIZE (8) */
0x95, 0x06, /* REPORT_COUNT (6) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x65, /* LOGICAL_MAXIMUM (101) */
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
0x19, 0x00, /* USAGE_MINIMUM (0) */
0x29, 0x65, /* USAGE_MAXIMUM (101) */
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
0xC0, /* END_COLLECTION */
};
UsbHidDevice::HidDescriptor hid_descriptor = {
.bLength = sizeof(UsbHidDevice::HidDescriptor),
.bDescriptorType = UsbHidDevice::kDescriptorTypeHid,
.bcdHID = 0x0110,
.bCountryCode = 0x00,
.bNumDescriptors = 1,
.bReportDescriptorType = UsbHidDevice::kDescriptorTypeReport,
.wDescriptorLength = static_cast<uint16_t>(report_descriptor.size())};
UsbDevice::ConfigurationDescriptor configuration_descriptor = {
.bLength = sizeof(configuration_descriptor),
.bDescriptorType = UsbDevice::kDescriptorTypeConfiguration,
.wTotalLength = static_cast<uint16_t>(
sizeof(UsbDevice::ConfigurationDescriptor) +
sizeof(UsbDevice::InterfaceDescriptor) +
sizeof(UsbDevice::EndPointDescriptor) * endpoint_descriptors.size() +
sizeof(UsbHidDevice::HidDescriptor)),
.bNumInterfaces = static_cast<uint8_t>(interface_descriptors.size()),
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = 0xc0,
.bMaxPower = 250};
std::vector<std::string> strings = {"", "", ""};
} // namespace
UsbHidKeyboard::UsbHidKeyboard(uint16_t vendor_id,
uint16_t product_id,
uint16_t version,
std::string vendor_name,
std::string product_name,
std::string version_name)
: UsbHidDevice(device_descriptor,
configuration_descriptor,
interface_descriptors,
endpoint_descriptors,
hid_descriptor,
report_descriptor,
strings) {
device_descriptor.idVendor = vendor_id;
device_descriptor.idProduct = product_id;
device_descriptor.bcdDevice = version;
strings[kManufacturerStringIndex - 1] = vendor_name;
strings[kProductStringIndex - 1] = product_name;
strings[kSerialNumberStringIndex - 1] = version_name;
report_.resize(8);
}
void UsbHidKeyboard::SetAutoKeyRelease(bool enabled) {
auto_key_release_ = enabled;
}
void UsbHidKeyboard::SetModifiers(uint8_t modifiers) {
modifiers_ = modifiers;
}
void UsbHidKeyboard::PressByUsageId(uint8_t usage_id) {
keycodes_.insert(usage_id);
if (reporting_) {
dirty_ = true;
} else {
Report();
}
}
void UsbHidKeyboard::ReleaseByUsageId(uint8_t usage_id) {
auto it = keycodes_.find(usage_id);
if (it != keycodes_.end()) {
keycodes_.erase(it);
if (reporting_) {
dirty_ = true;
} else {
Report();
}
}
}
void UsbHidKeyboard::OnCompleteToSend(uint8_t endpoint) {
reporting_ = false;
if (dirty_) {
Report();
}
}
void UsbHidKeyboard::Report() {
report_[0] = modifiers_;
report_[1] = 0x00;
if (keycodes_.size() > 6) {
// Phantom state
for (size_t i = 2; i < report_.size(); ++i) {
report_[i] = 0x01;
}
} else {
size_t i = 2;
for (size_t keycode : keycodes_) {
report_[i++] = keycode;
}
while (i < 8) {
report_[i++] = 0x00;
}
}
dirty_ = false;
if (auto_key_release_ && !keycodes_.empty()) {
keycodes_.clear();
modifiers_ = 0;
dirty_ = true;
}
reporting_ = true;
UsbHidDevice::Report(1, report_);
}

View File

@@ -0,0 +1,49 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef COMMON_USB_HID_KEYBOARD_H_
#define COMMON_USB_HID_KEYBOARD_H_
#include <cstdint>
#include <set>
#include <string>
#include <vector>
#include "usb_hid_device.h"
class UsbHidKeyboard : public UsbHidDevice {
public:
UsbHidKeyboard(uint16_t vendor_id,
uint16_t product_id,
uint16_t version,
std::string vendor_name,
std::string product_name,
std::string version_name);
UsbHidKeyboard(const UsbHidKeyboard&) = delete;
UsbHidKeyboard& operator=(const UsbHidKeyboard&) = delete;
~UsbHidKeyboard() override = default;
void SetAutoKeyRelease(bool enabled);
void SetModifiers(uint8_t modifiers);
uint8_t GetModifiers() const { return modifiers_; }
void PressByUsageId(uint8_t usage_id);
void ReleaseByUsageId(uint8_t usage_id);
private:
// Implement UsbDevice.
void OnCompleteToSend(uint8_t endpoint) override;
void Report();
bool auto_key_release_ = false;
bool reporting_ = false;
bool dirty_ = false;
uint8_t modifiers_ = 0;
std::set<uint8_t> keycodes_;
std::vector<uint8_t> report_;
};
#endif // COMMON_USB_HID_KEYBOARD_H_

View File

@@ -0,0 +1,78 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD none CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(main C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(main
../common/dial_controller.cc
../common/dial_controller.h
../common/photo_sensor.cc
../common/photo_sensor.h
../common/usage_tables.cc
../common/usage_tables.h
../common/usb_device.cc
../common/usb_device.h
../common/usb_hid_device.cc
../common/usb_hid_device.h
../common/usb_hid_keyboard.cc
../common/usb_hid_keyboard.h
i2c_controller.cc
i2c_controller.h
main.cc
)
pico_set_program_name(main "main")
pico_set_program_version(main "0.1")
# Modify the below lines to enable/disable output over UART/USB/SEMIHOSTING
pico_enable_stdio_uart(main 1)
pico_enable_stdio_usb(main 0)
pico_enable_stdio_semihosting(main 0)
# Add the standard library to the build
target_link_libraries(main
pico_stdlib)
# Add the standard include files to the build
target_include_directories(main PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# Add any user requested libraries
target_link_libraries(main
hardware_i2c
)
pico_add_extra_outputs(main)

View File

@@ -0,0 +1,47 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "i2c_controller.h"
#include <algorithm>
#include <vector>
#include "hardware/gpio.h"
static constexpr int kI2CSpeedInHz = 400 * 1000;
I2CController::I2CController(i2c_inst_t* i2c, int8_t sda, int8_t scl)
: i2c_(i2c) {
hard_assert(i2c == i2c0 || i2c == i2c1);
gpio_init(sda);
gpio_init(scl);
gpio_set_function(sda, GPIO_FUNC_I2C);
gpio_set_function(scl, GPIO_FUNC_I2C);
gpio_pull_up(sda);
gpio_pull_up(scl);
i2c_init(i2c_, kI2CSpeedInHz);
}
bool I2CController::Read(uint8_t device_addr,
uint8_t mem_addr,
std::span<uint8_t> data) {
uint8_t addr[1] = {mem_addr};
int result = i2c_write_blocking(i2c_, device_addr, addr, 1, true);
if (result != 1) {
return false;
}
result = i2c_read_blocking(i2c_, device_addr, data.data(), data.size(), false);
return result == data.size();
}
bool I2CController::Write(uint8_t device_addr,
uint8_t mem_addr,
std::span<uint8_t> data) {
std::vector<uint8_t> buf;
buf.reserve(data.size() + 1);
buf.push_back(mem_addr);
buf.insert(buf.end(), data.begin(), data.end());
int rc = i2c_write_blocking(i2c_, device_addr, buf.data(), buf.size(), false);
return rc == buf.size();
}

View File

@@ -0,0 +1,27 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef I2C_CONTROLLER_H_
#define I2C_CONTROLLER_H_
#include <cstdint>
#include <span>
#include "hardware/i2c.h"
class I2CController final {
public:
I2CController(i2c_inst_t* i2c, int8_t sda, int8_t scl);
I2CController(const I2CController&) = delete;
I2CController& operator=(const I2CController&) = delete;
~I2CController() = default;
bool Write(uint8_t device_addr, uint8_t mem_addr, std::span<uint8_t> data);
bool Read(uint8_t device_addr, uint8_t mem_addr, std::span<uint8_t> data);
private:
i2c_inst_t* i2c_ = nullptr;
};
#endif // I2C_CONTROLLER_H_

View File

@@ -0,0 +1,170 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include <array>
#include <cstdint>
#include <cstdio>
#include <vector>
#include "hardware/uart.h"
#if LIB_PICO_STDIO_UART
#include "pico/stdio_uart.h"
#endif
#include "pico/stdlib.h"
#include "../common/dial_controller.h"
#include "../common/motor_controller.h"
#include "../common/photo_sensor.h"
#include "../common/usage_tables.h"
#include "../common/usb_hid_keyboard.h"
#include "i2c_controller.h"
int main() {
#if LIB_PICO_STDIO_UART
stdio_uart_init_full(uart0, /*baud_rate=*/115200, /*tx_pin=*/0,
/*rx_pin=*/-1);
#else
stdio_init_all();
#endif
I2CController i2c(i2c0, /*sda=*/28, /*scl=*/29);
// Sensor A, B, C, D, E, F, G, I (note: H is missing here as it's accessed via
// I2C)
std::array<PhotoSensor, 8> sensors = {
PhotoSensor(1, 2, 3, 4, 5, 6), // A
PhotoSensor(7, 8), // B
PhotoSensor(9, 10, 11), // C
PhotoSensor(12, 13), // D (3 sensors are implemented, but...)
PhotoSensor(15, 16, 17), // E
PhotoSensor(18, 19, 20), // F
PhotoSensor(21, 22), // G (3 sensors are implemented, but...)
PhotoSensor(24, 25, 26, 27), // I
};
std::array<DialController, 9> dials = {
DialController(), // A
DialController(), // B
DialController(), // C
DialController(), // D
DialController(), // E
DialController(), // F
DialController(), // G
DialController(), // I
DialController(), // H
};
std::array<const std::vector<uint8_t>*, 9> usages = {
&usage_tables::a, // A
&usage_tables::b, // B
&usage_tables::c, // C
&usage_tables::d, // D
&usage_tables::e, // E
&usage_tables::f, // F
&usage_tables::g, // G
&usage_tables::i, // I
&usage_tables::h, // H
};
std::array<const std::vector<uint8_t>*, 9> fn_usages = {
&usage_tables::fn_a, // A
&usage_tables::fn_b, // B
&usage_tables::fn_c, // C
&usage_tables::fn_d, // D
&usage_tables::fn_e, // E
&usage_tables::fn_f, // F
&usage_tables::fn_g, // G
&usage_tables::fn_i, // I
&usage_tables::fn_h, // H
};
std::array<const std::vector<uint8_t>*, 9> modifiers = {
&usage_tables::modifier_a, // A
&usage_tables::modifier_b, // B
&usage_tables::modifier_c, // C
&usage_tables::modifier_d, // D
&usage_tables::modifier_e, // E
&usage_tables::modifier_f, // F
&usage_tables::modifier_g, // G
&usage_tables::modifier_i, // I
&usage_tables::modifier_h, // H
};
UsbHidKeyboard usb_hid_keyboard(
/*vendor_id=*/0x6666, /*product_id=*/0x2025,
/*version=*/0x0109, /*vendor_name=*/"Gboard DIY prototype",
/*product_name=*/"Gboard Dial version", /*version_name=*/"9 Dial");
usb_hid_keyboard.SetAutoKeyRelease(true);
// Wait for a while, just in case, so that the sub-controller can be ready on
// I2C.
sleep_ms(100);
std::vector<uint8_t> i2c_buffer(2);
bool fn = false;
while (true) {
uint16_t motor_start_bitmap = 0;
for (size_t i = 0; i < sensors.size(); ++i) {
dials[i].Update(sensors[i].Read());
if (!dials[i].IsBasePosition()) {
motor_start_bitmap |= (1 << i);
}
}
// Read the remote sensor H value via I2C.
bool rc = i2c.Read(68, 0, std::span<uint8_t>({i2c_buffer.data(), 1}));
if (rc) {
dials[8].Update(i2c_buffer[0]);
if (!dials[8].IsBasePosition()) {
motor_start_bitmap |= (1 << 8);
}
}
// Drive all motors via I2C.
i2c_buffer[0] = motor_start_bitmap & 0xff;
i2c_buffer[1] = motor_start_bitmap >> 8;
rc = i2c.Write(68, 0, i2c_buffer);
// Check all dials to see if we have pending inputs to send over USB HID.
for (size_t i = 0; i < dials.size(); ++i) {
// `decided_position` is std::nullopt while the dial is not moved at the
// base position, or moving. But once it returns to the base position from
// non-base positions, it will have a 1 or a larger value that indicates
// the position where the dial starts returning.
std::optional<uint8_t> decided_position = dials[i].PopDecidedPosition();
if (!decided_position) {
continue;
}
// One-time flip to use an alternative set of usages, by the Fn key.
std::array<const std::vector<uint8_t>*, 9>& current_usages =
fn ? fn_usages : usages;
// Adjust the index as it is 1-based.
size_t index = *decided_position - 1;
if (index < current_usages[i]->size()) {
uint8_t usage = (*current_usages[i])[index];
if (usage) {
usb_hid_keyboard.PressByUsageId(usage);
fn = false;
}
}
if (index < modifiers[i]->size()) {
uint8_t modifier = (*modifiers[i])[index];
if (modifier == usage_tables::kModifierLeftGUI) {
// This device interprets the left GUI key as a one-time keymap
// alternate key instead of the normal GUI modifier key use.
fn = true;
} else {
usb_hid_keyboard.SetModifiers(
(usb_hid_keyboard.GetModifiers() | modifier));
}
}
}
}
}

View File

@@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

View File

@@ -0,0 +1,73 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD pico CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(one_dial C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(one_dial
../common/dial_controller.cc
../common/dial_controller.h
../common/motor_controller.cc
../common/motor_controller.h
../common/photo_sensor.cc
../common/photo_sensor.h
../common/usage_tables.cc
../common/usage_tables.h
../common/usb_device.cc
../common/usb_device.h
../common/usb_hid_device.cc
../common/usb_hid_device.h
../common/usb_hid_keyboard.cc
../common/usb_hid_keyboard.h
one_dial.cc
)
pico_set_program_name(one_dial "one_dial")
pico_set_program_version(one_dial "0.1")
# Modify the below lines to enable/disable output over UART/USB/SEMIHOSTING
pico_enable_stdio_uart(one_dial 1)
pico_enable_stdio_usb(one_dial 0)
pico_enable_stdio_semihosting(one_dial 0)
# Add the standard library to the build
target_link_libraries(one_dial
pico_stdlib)
# Add the standard include files to the build
target_include_directories(one_dial PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
pico_add_extra_outputs(one_dial)

View File

@@ -0,0 +1,56 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include <cstdint>
#include <cstdio>
#include <optional>
#include "pico/stdlib.h"
#include "../common/dial_controller.h"
#include "../common/motor_controller.h"
#include "../common/photo_sensor.h"
#include "../common/usage_tables.h"
#include "../common/usb_hid_keyboard.h"
int main() {
// GPIO0 and 1 are used for stdout, and stdin by default.
stdio_init_all();
// GPIO17, 18, 19, and 20 are used for motor phase control.
MotorController motor_controller(MotorController::Mode::k1Motor);
// GPIO2, 3, 4, 5, 6, and 7 are used for 6bit photo sensing.
PhotoSensor photo_sensor(2, 3, 4, 5, 6, 7);
DialController dial_controller;
UsbHidKeyboard usb_hid_keyboard(
/*vendor_id=*/0x6666, /*product_id=*/0x2025,
/*version=*/0x0101, /*vendor_name=*/"Gboard DIY prototype",
/*product_name=*/"Gboard Dial version", /*version_name=*/"1 Dial");
usb_hid_keyboard.SetAutoKeyRelease(true);
while (true) {
dial_controller.Update(photo_sensor.Read());
if (dial_controller.IsBasePosition()) {
motor_controller.Stop(8);
} else {
motor_controller.Start(8);
}
// `decided_position` is std::nullopt while the dial is not moved at the
// base position, or moving. But once it returns to the base position from
// non-base positions, it will have a 1 or a larger value that indicates the
// position where the dial starts returning.
std::optional<uint8_t> decided_position =
dial_controller.PopDecidedPosition();
if (decided_position && *decided_position <= usage_tables::a.size()) {
// Adjust the index as it is 1-based.
uint8_t usage = usage_tables::a[*decided_position - 1];
if (usage) {
usb_hid_keyboard.PressByUsageId(usage);
}
}
}
}

View File

@@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,75 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
if(WIN32)
set(USERHOME $ENV{USERPROFILE})
else()
set(USERHOME $ENV{HOME})
endif()
set(sdkVersion 2.2.0)
set(toolchainVersion 14_2_Rel1)
set(picotoolVersion 2.2.0)
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
if (EXISTS ${picoVscode})
include(${picoVscode})
endif()
# ====================================================================================
set(PICO_BOARD none CACHE STRING "Board type")
#set(PICO_BOARD pico CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(sub C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(sub
../common/motor_controller.cc
../common/motor_controller.h
../common/photo_sensor.cc
../common/photo_sensor.h
i2c_device.cc
i2c_device.h
sub.cc
)
pico_set_program_name(sub "sub")
pico_set_program_version(sub "0.1")
# Modify the below lines to enable/disable output over UART/USB/SEMIHOSTING
pico_enable_stdio_uart(sub 1)
pico_enable_stdio_usb(sub 0)
pico_enable_stdio_semihosting(sub 0)
# Add the standard library to the build
target_link_libraries(sub
pico_i2c_slave
pico_stdlib
)
# Add the standard include files to the build
target_include_directories(sub PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# Add any user requested libraries
target_link_libraries(sub
hardware_i2c
hardware_timer
hardware_clocks
)
pico_add_extra_outputs(sub)

View File

@@ -0,0 +1,73 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include "i2c_device.h"
#include <cstdio>
#include "hardware/gpio.h"
#include "hardware/i2c.h"
#include "pico/i2c_slave.h"
static constexpr int kI2CSpeedInHz = 400 * 1000;
namespace {
I2CDevice* sI2CDevice = nullptr;
}; // namespace
// static
void I2CDevice::HandleEvent(i2c_inst_t* i2c, i2c_slave_event_t event) {
sI2CDevice->HandleEventInternal(i2c, event);
}
I2CDevice::I2CDevice(i2c_inst_t* i2c,
uint8_t sda,
uint8_t scl,
uint8_t address) {
hard_assert(sI2CDevice == nullptr);
sI2CDevice = this;
gpio_init(sda);
gpio_init(scl);
gpio_set_function(sda, GPIO_FUNC_I2C);
gpio_set_function(scl, GPIO_FUNC_I2C);
gpio_pull_up(sda);
gpio_pull_up(scl);
i2c_init(i2c, kI2CSpeedInHz);
i2c_slave_init(i2c, address, &I2CDevice::HandleEvent);
}
void I2CDevice::SetReadHandler(ReadHandler handler) {
reader_ = handler;
}
void I2CDevice::SetWriteHandler(WriteHandler handler) {
writer_ = handler;
}
void I2CDevice::HandleEventInternal(i2c_inst_t* i2c, i2c_slave_event_t event) {
switch (event) {
case I2C_SLAVE_RECEIVE:
if (!address_ready_) {
address_ = i2c_read_byte_raw(i2c);
address_ready_ = true;
} else {
if (writer_) {
writer_(address_, i2c_read_byte_raw(i2c));
} else {
printf("ignore write at $%02x: $%02x\n", address_, i2c_read_byte_raw(i2c));
}
++address_;
}
break;
case I2C_SLAVE_REQUEST:
i2c_write_byte_raw(i2c, reader_ ? reader_(address_) : 0);
address_++;
break;
case I2C_SLAVE_FINISH:
address_ready_ = false;
break;
}
}

View File

@@ -0,0 +1,37 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#ifndef I2C_DEVICE_H_
#define I2C_DEVICE_H_
#include <cstdint>
#include <functional>
#include "hardware/i2c.h"
#include "pico/i2c_slave.h"
class I2CDevice final {
public:
using ReadHandler = std::function<uint8_t(uint8_t /*address*/)>;
using WriteHandler = std::function<void(uint8_t /*address*/, uint8_t /*value*/)>;
I2CDevice(i2c_inst_t* i2c, uint8_t sda, uint8_t scl, uint8_t address);
I2CDevice(const I2CDevice&) = delete;
I2CDevice& operator=(const I2CDevice&) = delete;
~I2CDevice() = default;
void SetReadHandler(ReadHandler handler);
void SetWriteHandler(WriteHandler handler);
private:
static void HandleEvent(i2c_inst_t* i2c, i2c_slave_event_t event);
void HandleEventInternal(i2c_inst_t* i2c, i2c_slave_event_t event);
ReadHandler reader_ = nullptr;
WriteHandler writer_ = nullptr;
bool address_ready_ = false;
uint8_t address_ = 0;
};
#endif // I2C_DEVICE_H_

View File

@@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

View File

@@ -0,0 +1,70 @@
// Copyright 2025 Google Inc.
// Use of this source code is governed by an Apache License that can be found
// in the LICENSE file.
#include <cstdio>
#include "hardware/uart.h"
#include "pico/stdio.h"
#if LIB_PICO_STDIO_UART
#include "pico/stdio_uart.h"
#endif
#include "../common/motor_controller.h"
#include "../common/photo_sensor.h"
#include "i2c_device.h"
namespace {
MotorController motor_controller;
PhotoSensor sensor_h(26, 27);
uint8_t i2c_reader(uint8_t address) {
switch (address) {
case 0: // Read Sensor H
return sensor_h.Read();
default:
return 0xff;
}
}
void i2c_writer(uint8_t address, uint8_t value) {
switch (address) {
case 0: // Set Motor 1-8 State
for (uint8_t motor = 0; motor < 8; ++motor) {
if (value & (1 << motor)) {
motor_controller.Start(motor);
} else {
motor_controller.Stop(motor);
}
}
break;
case 1: // Set Motor 9 State
if (value & 1) {
motor_controller.Start(8);
} else {
motor_controller.Stop(8);
}
break;
}
}
} // namespace
int main() {
#if LIB_PICO_STDIO_UART
// Customized initialization to use only GPIO 0 for TX.
stdio_uart_init_full(uart0, /*baud_rate=*/115200, /*tx_pin=*/0,
/*rx_pin=*/-1);
#else
stdio_init_all();
#endif
I2CDevice i2c(/*i2c=*/i2c0, /*sda=*/28, /*scl=*/29, /*address=*/68);
i2c.SetReadHandler(i2c_reader);
i2c.SetWriteHandler(i2c_writer);
while (true) {
tight_loop_contents();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

BIN
mozc-dial/images/dial.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
mozc-dial/images/diy.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,101 @@
<svg width="100mm" height="100mm" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50.0" cy="50.0" r="8" stroke="black" stroke-width="0.1" fill="none" />
<circle cx="50.0" cy="50.0" r="38" stroke="black" stroke-width="0.1" fill="none" />
<polygon points="72.60326589655097,19.453439296545742 72.98170344432137,17.26101243475498 74.58924970060986,18.450534090716246" fill="black" />
<path d="M 54.446261863233694 37.78399592978319 A 13 13 0 0 0 50.0 37.0 L 50.0 42.0 A 8 8 0 0 1 52.73616114660535 42.482459033712736 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 56.03624925742874 38.48636916945002 A 13 13 0 0 0 54.446261863233694 37.78399592978319 L 52.73616114660535 42.482459033712736 A 8 8 0 0 1 53.714614927648455 42.914688719661555 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 58.35788358720903 34.058049619238496 A 18 18 0 0 0 56.15636257986204 33.08553282585365 L 54.446261863233694 37.78399592978319 A 13 13 0 0 1 56.03624925742874 38.48636916945002 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 60.40998205102376 35.31557717520491 A 18 18 0 0 0 58.35788358720903 34.058049619238496 L 56.03624925742874 38.48636916945002 A 13 13 0 0 1 57.51832037018382 39.394583515425765 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 62.27597048112497 36.835633370854936 A 18 18 0 0 0 60.40998205102376 35.31557717520491 L 57.51832037018382 39.394583515425765 A 13 13 0 0 1 58.86597868081248 40.49240187895079 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 65.68596228143747 33.178864862759085 A 23 23 0 0 0 63.30164373186369 31.236570834984043 L 60.40998205102376 35.31557717520491 A 18 18 0 0 1 62.27597048112497 36.835633370854936 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 60.05513070554153 41.76019742381677 A 13 13 0 0 0 58.86597868081248 40.49240187895079 L 55.455986880499985 44.14917038704664 A 8 8 0 0 1 56.18777274187171 44.92935226081032 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 63.922488669211354 38.59104258682322 A 18 18 0 0 0 62.27597048112497 36.835633370854936 L 58.86597868081248 40.49240187895079 A 13 13 0 0 1 60.05513070554153 41.76019742381677 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 67.78984663288117 35.421887749829665 A 23 23 0 0 0 65.68596228143747 33.178864862759085 L 62.27597048112497 36.835633370854936 A 18 18 0 0 1 63.922488669211354 38.59104258682322 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 61.064516741681 43.17530445565072 A 13 13 0 0 0 60.05513070554153 41.76019742381677 L 56.18777274187171 44.92935226081032 A 8 8 0 0 1 56.808933379496 45.800187357323516 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 69.57568346605099 37.925538652305114 A 23 23 0 0 0 67.78984663288117 35.421887749829665 L 63.922488669211354 38.59104258682322 A 18 18 0 0 1 65.320100103866 40.55042155397791 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 71.01154552577982 40.64505720925659 A 23 23 0 0 0 69.57568346605099 37.925538652305114 L 65.320100103866 40.55042155397791 A 18 18 0 0 1 66.44381823756682 42.6787404246356 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 72.07176241849403 43.53182377006049 A 23 23 0 0 0 71.01154552577982 40.64505720925659 L 66.44381823756682 42.6787404246356 A 18 18 0 0 1 67.2735531970823 44.93794903743865 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 76.86997163990577 42.12569850268234 A 28 28 0 0 0 75.57927281399282 38.61137399387759 L 71.01154552577982 40.64505720925659 A 23 23 0 0 1 72.07176241849403 43.53182377006049 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 62.851562353462455 48.04108574075672 A 13 13 0 0 0 62.47534397567054 46.3440743048168 L 57.677134754258795 47.75019957219495 A 8 8 0 0 1 57.90865375597689 48.79451430200414 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 72.73737954843357 46.53422861826189 A 23 23 0 0 0 72.07176241849403 43.53182377006049 L 67.2735531970823 44.93794903743865 A 18 18 0 0 1 67.79447095094801 47.28765717950931 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 77.68028814591912 45.780800057014474 A 28 28 0 0 0 76.86997163990577 42.12569850268234 L 72.07176241849403 43.53182377006049 A 23 23 0 0 1 72.73737954843357 46.53422861826189 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 62.998020037033086 49.77311871631532 A 13 13 0 0 0 62.851562353462455 48.04108574075672 L 57.90865375597689 48.79451430200414 A 8 8 0 0 1 57.99878156125113 49.86038074850173 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 67.99725851281505 49.68585668412889 A 18 18 0 0 0 67.79447095094801 47.28765717950931 L 62.851562353462455 48.04108574075672 A 13 13 0 0 1 62.998020037033086 49.77311871631532 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 72.996496988597 49.59859465194248 A 23 23 0 0 0 72.73737954843357 46.53422861826189 L 67.79447095094801 47.28765717950931 A 18 18 0 0 1 67.99725851281505 49.68585668412889 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 77.99573546437895 49.51133261975606 A 28 28 0 0 0 77.68028814591912 45.780800057014474 L 72.73737954843357 46.53422861826189 A 23 23 0 0 1 72.996496988597 49.59859465194248 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 67.87829043935497 52.08967245425414 A 18 18 0 0 0 67.99725851281505 49.68585668412889 L 62.998020037033086 49.77311871631532 A 13 13 0 0 1 62.91209865064526 51.509207883627994 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 72.84448222806469 52.6701370248803 A 23 23 0 0 0 72.996496988597 49.59859465194248 L 67.99725851281505 49.68585668412889 A 18 18 0 0 1 67.87829043935497 52.08967245425414 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 77.81067401677441 53.250601595506446 A 28 28 0 0 0 77.99573546437895 49.51133261975606 L 72.996496988597 49.59859465194248 A 23 23 0 0 1 72.84448222806469 52.6701370248803 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 67.43969364606622 54.45612898504495 A 18 18 0 0 0 67.87829043935497 52.08967245425414 L 62.91209865064526 51.509207883627994 A 13 13 0 0 1 62.59533429993671 53.218315378088015 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 77.12841233832522 56.93175619895881 A 28 28 0 0 0 77.81067401677441 53.250601595506446 L 72.84448222806469 52.6701370248803 A 23 23 0 0 1 72.28405299219571 55.693942592001875 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 62.05339010936824 54.869885714406855 A 13 13 0 0 0 62.59533429993671 53.218315378088015 L 57.75097495380721 51.98050177113109 A 8 8 0 0 1 57.4174708365343 52.996852747327296 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 66.68930938220217 56.742918681486415 A 18 18 0 0 0 67.43969364606622 54.45612898504495 L 62.59533429993671 53.218315378088015 A 13 13 0 0 1 62.05339010936824 54.869885714406855 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 75.96114792787004 60.488984615645535 A 28 28 0 0 0 77.12841233832522 56.93175619895881 L 72.28405299219571 55.693942592001875 A 23 23 0 0 1 71.32522865503611 58.615951648565975 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 61.29595497668079 56.434392058679705 A 13 13 0 0 0 62.05339010936824 54.869885714406855 L 57.4174708365343 52.996852747327296 A 8 8 0 0 1 56.95135690872664 53.95962588226443 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 74.32974918054325 63.85869058792552 A 28 28 0 0 0 75.96114792787004 60.488984615645535 L 71.32522865503611 58.615951648565975 A 23 23 0 0 1 69.9851511125891 61.38392441151025 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 72.26338230185574 66.98063039116677 A 28 28 0 0 0 74.32974918054325 63.85869058792552 L 69.9851511125891 61.38392441151025 A 23 23 0 0 1 68.2877783193815 63.94837496417271 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 69.79898987322333 69.79898987322332 A 28 28 0 0 0 72.26338230185574 66.98063039116677 L 68.2877783193815 63.94837496417271 A 23 23 0 0 1 66.2634559672906 66.2634559672906 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 73.33452377915607 73.33452377915606 A 33 33 0 0 0 76.23898628432998 70.01288581816084 L 72.26338230185574 66.98063039116677 A 28 28 0 0 1 69.79898987322333 69.79898987322332 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 57.883864110184575 60.33657035443302 A 13 13 0 0 0 59.19238815542512 59.19238815542512 L 55.65685424949238 55.65685424949238 A 8 8 0 0 1 54.851608683190506 56.36096637195878 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 66.98063039116677 72.26338230185574 A 28 28 0 0 0 69.79898987322333 69.79898987322332 L 66.2634559672906 66.2634559672906 A 23 23 0 0 1 63.9483749641727 68.2877783193815 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 70.01288581816084 76.23898628432998 A 33 33 0 0 0 73.33452377915607 73.33452377915606 L 69.79898987322333 69.79898987322332 A 28 28 0 0 1 66.98063039116677 72.26338230185574 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 56.434392058679705 61.29595497668079 A 13 13 0 0 0 57.883864110184575 60.33657035443302 L 54.851608683190506 56.36096637195878 A 8 8 0 0 1 53.95962588226443 56.95135690872664 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 58.90915823509498 65.64055304463494 A 18 18 0 0 0 60.91611953717864 64.31217433690726 L 57.883864110184575 60.33657035443302 A 13 13 0 0 1 56.434392058679705 61.29595497668079 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 63.858690587925516 74.32974918054325 A 28 28 0 0 0 66.98063039116677 72.26338230185574 L 63.9483749641727 68.2877783193815 A 23 23 0 0 1 61.38392441151024 69.9851511125891 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 66.33345676434078 78.6743472484974 A 33 33 0 0 0 70.01288581816084 76.23898628432998 L 66.98063039116677 72.26338230185574 A 28 28 0 0 1 63.858690587925516 74.32974918054325 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 56.742918681486415 66.68930938220217 A 18 18 0 0 0 58.90915823509498 65.64055304463494 L 56.434392058679705 61.29595497668079 A 13 13 0 0 1 54.869885714406855 62.05339010936824 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 60.488984615645535 75.96114792787004 A 28 28 0 0 0 63.858690587925516 74.32974918054325 L 61.38392441151024 69.9851511125891 A 23 23 0 0 1 58.615951648565975 71.32522865503611 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 62.362017582725095 80.59706720070399 A 33 33 0 0 0 66.33345676434078 78.6743472484974 L 63.858690587925516 74.32974918054325 A 28 28 0 0 1 60.488984615645535 75.96114792787004 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 54.45612898504495 67.43969364606622 A 18 18 0 0 0 56.742918681486415 66.68930938220217 L 54.869885714406855 62.05339010936824 A 13 13 0 0 1 53.21831537808802 62.59533429993671 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 55.69394259200188 72.28405299219571 A 23 23 0 0 0 58.615951648565975 71.32522865503611 L 56.742918681486415 66.68930938220217 A 18 18 0 0 1 54.45612898504495 67.43969364606622 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 56.931756198958816 77.12841233832522 A 28 28 0 0 0 60.488984615645535 75.96114792787004 L 58.615951648565975 71.32522865503611 A 23 23 0 0 1 55.69394259200188 72.28405299219571 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 58.16956980591574 81.97277168445473 A 33 33 0 0 0 62.362017582725095 80.59706720070399 L 60.488984615645535 75.96114792787004 A 28 28 0 0 1 56.931756198958816 77.12841233832522 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 51.509207883627994 62.91209865064526 A 13 13 0 0 0 53.21831537808802 62.59533429993671 L 51.98050177113109 57.75097495380721 A 8 8 0 0 1 50.928743313001846 57.94590686193554 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 52.08967245425414 67.87829043935497 A 18 18 0 0 0 54.45612898504495 67.43969364606622 L 53.21831537808802 62.59533429993671 A 13 13 0 0 1 51.509207883627994 62.91209865064526 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 52.6701370248803 72.84448222806469 A 23 23 0 0 0 55.69394259200188 72.28405299219571 L 54.45612898504495 67.43969364606622 A 18 18 0 0 1 52.08967245425414 67.87829043935497 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 53.250601595506446 77.81067401677441 A 28 28 0 0 0 56.931756198958816 77.12841233832522 L 55.69394259200188 72.28405299219571 A 23 23 0 0 1 52.6701370248803 72.84448222806469 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 53.8310661661326 82.77686580548412 A 33 33 0 0 0 58.16956980591574 81.97277168445473 L 56.931756198958816 77.12841233832522 A 28 28 0 0 1 53.250601595506446 77.81067401677441 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 49.77311871631532 62.998020037033086 A 13 13 0 0 0 51.509207883627994 62.91209865064526 L 50.928743313001846 57.94590686193554 A 8 8 0 0 1 49.86038074850173 57.99878156125113 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 49.59859465194248 72.996496988597 A 23 23 0 0 0 52.6701370248803 72.84448222806469 L 52.08967245425414 67.87829043935497 A 18 18 0 0 1 49.6858566841289 67.99725851281505 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 49.51133261975606 77.99573546437895 A 28 28 0 0 0 53.250601595506446 77.81067401677441 L 52.6701370248803 72.84448222806469 A 23 23 0 0 1 49.59859465194248 72.996496988597 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 49.424070587569645 82.99497394016092 A 33 33 0 0 0 53.8310661661326 82.77686580548412 L 53.250601595506446 77.81067401677441 A 28 28 0 0 1 49.51133261975606 77.99573546437895 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 46.53422861826189 72.73737954843357 A 23 23 0 0 0 49.59859465194248 72.996496988597 L 49.6858566841289 67.99725851281505 A 18 18 0 0 1 47.28765717950931 67.79447095094801 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 45.78080005701448 77.68028814591912 A 28 28 0 0 0 49.51133261975606 77.99573546437895 L 49.59859465194248 72.996496988597 A 23 23 0 0 1 46.53422861826189 72.73737954843357 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 45.02737149576706 82.62319674340469 A 33 33 0 0 0 49.424070587569645 82.99497394016092 L 49.51133261975606 77.99573546437895 A 28 28 0 0 1 45.78080005701448 77.68028814591912 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 43.53182377006049 72.07176241849403 A 23 23 0 0 0 46.53422861826189 72.73737954843357 L 47.28765717950931 67.79447095094801 A 18 18 0 0 1 44.93794903743864 67.2735531970823 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 40.71957323530418 81.66818086131752 A 33 33 0 0 0 45.02737149576706 82.62319674340469 L 45.78080005701448 77.68028814591912 A 28 28 0 0 1 42.12569850268234 76.86997163990577 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 44.7124236400146 61.87609094935381 A 13 13 0 0 0 46.3440743048168 62.47534397567054 L 47.75019957219495 57.677134754258795 A 8 8 0 0 1 46.7461068553936 57.30836366114081 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 40.6450572092566 71.01154552577982 A 23 23 0 0 0 43.53182377006049 72.07176241849403 L 44.93794903743864 67.2735531970823 A 18 18 0 0 1 42.6787404246356 66.44381823756682 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 36.5776907784986 80.14700010220584 A 33 33 0 0 0 40.71957323530418 81.66818086131752 L 42.12569850268234 76.86997163990577 A 28 28 0 0 1 38.6113739938776 75.57927281399283 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 43.17530445565072 61.064516741681 A 13 13 0 0 0 44.7124236400146 61.87609094935381 L 46.7461068553936 57.30836366114081 A 8 8 0 0 1 45.80018735732352 56.808933379496 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 40.55042155397792 65.320100103866 A 18 18 0 0 0 42.6787404246356 66.44381823756682 L 44.7124236400146 61.87609094935381 A 13 13 0 0 1 43.17530445565072 61.064516741681 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 37.92553865230512 69.57568346605099 A 23 23 0 0 0 40.6450572092566 71.01154552577982 L 42.6787404246356 66.44381823756682 A 18 18 0 0 1 40.55042155397792 65.320100103866 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 32.67577284895952 78.08685019042099 A 33 33 0 0 0 36.5776907784986 80.14700010220584 L 38.6113739938776 75.57927281399283 A 28 28 0 0 1 35.300655750632316 73.831266828236 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 38.591042586823214 63.92248866921135 A 18 18 0 0 0 40.55042155397792 65.320100103866 L 43.17530445565072 61.064516741681 A 13 13 0 0 1 41.76019742381676 60.05513070554153 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 35.421887749829665 67.78984663288117 A 23 23 0 0 0 37.92553865230512 69.57568346605099 L 40.55042155397792 65.320100103866 A 18 18 0 0 1 38.591042586823214 63.92248866921135 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 29.083578075842556 75.5245625602208 A 33 33 0 0 0 32.67577284895952 78.08685019042099 L 35.300655750632316 73.831266828236 A 28 28 0 0 1 32.25273291283611 71.657204596551 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 36.835633370854936 62.27597048112497 A 18 18 0 0 0 38.591042586823214 63.92248866921135 L 41.76019742381676 60.05513070554153 A 13 13 0 0 1 40.49240187895079 58.86597868081248 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 25.865327846567375 72.50594588206245 A 33 33 0 0 0 29.083578075842556 75.5245625602208 L 32.25273291283611 71.657204596551 A 28 28 0 0 1 29.522096354663226 69.09595408174997 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 39.394583515425765 57.51832037018383 A 13 13 0 0 0 40.49240187895079 58.86597868081248 L 44.14917038704664 55.45598688049999 A 8 8 0 0 1 43.47358985564662 54.62665868934389 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 35.31557717520491 60.40998205102376 A 18 18 0 0 0 36.835633370854936 62.27597048112497 L 40.49240187895079 58.86597868081248 A 13 13 0 0 1 39.394583515425765 57.51832037018383 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 23.07855815454233 69.08496709354355 A 33 33 0 0 0 25.865327846567375 72.50594588206245 L 29.522096354663226 69.09595408174997 A 28 28 0 0 1 27.15756449476319 66.19330541270364 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 38.48636916945002 56.03624925742874 A 13 13 0 0 0 39.394583515425765 57.51832037018383 L 43.47358985564662 54.62665868934389 A 8 8 0 0 1 42.914688719661555 53.714614927648455 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 20.7730909686039 65.32278657654987 A 33 33 0 0 0 23.07855815454233 69.08496709354355 L 27.15756449476319 66.19330541270364 A 28 28 0 0 1 25.20141051881543 63.00115224676959 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 18.990143514065025 61.28666472974707 A 33 33 0 0 0 20.7730909686039 65.32278657654987 L 25.20141051881543 63.00115224676959 A 28 28 0 0 1 23.688606617994566 59.57656401311873 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 17.761591389677662 57.04875948475295 A 33 33 0 0 0 18.990143514065025 61.28666472974707 L 23.688606617994566 59.57656401311873 A 28 28 0 0 1 22.646198754878018 55.98076562342674 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 12.87698402447731 58.11675334607915 A 38 38 0 0 0 14.291680410135484 62.99676544637542 L 18.990143514065025 61.28666472974707 A 33 33 0 0 1 17.761591389677662 57.04875948475295 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 37.04309645542142 51.05766277068215 A 13 13 0 0 0 37.30002085047908 52.77678403944813 L 42.18462821567943 51.70879017812192 A 8 8 0 0 1 42.026520895643955 50.650869397342866 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 17.1093986945313 52.68483626403931 A 33 33 0 0 0 17.761591389677662 57.04875948475295 L 22.646198754878018 55.98076562342674 A 28 28 0 0 1 22.09282313475383 52.27804289070002 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 12.125974254308773 53.0916296373786 A 38 38 0 0 0 12.87698402447731 58.11675334607915 L 17.761591389677662 57.04875948475295 A 33 33 0 0 1 17.1093986945313 52.68483626403931 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 37.01781604819054 49.31963256884173 A 13 13 0 0 0 37.04309645542142 51.05766277068215 L 42.026520895643955 50.650869397342866 A 8 8 0 0 1 42.01096372196341 49.58131235005645 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 32.02466837441767 49.057952787627016 A 18 18 0 0 0 32.05967201519889 51.46445614402144 L 37.04309645542142 51.05766277068215 A 13 13 0 0 1 37.01781604819054 49.31963256884173 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 17.045225353099063 48.272913443982866 A 33 33 0 0 0 17.1093986945313 52.68483626403931 L 22.09282313475383 52.27804289070002 A 28 28 0 0 1 22.038373026871934 48.53459322519758 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 12.052077679326196 48.01123366276814 A 38 38 0 0 0 12.125974254308773 53.0916296373786 L 17.1093986945313 52.68483626403931 A 33 33 0 0 1 17.045225353099063 48.272913443982866 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 32.31102835928456 46.66829138519513 A 18 18 0 0 0 32.02466837441767 49.057952787627016 L 37.01781604819054 49.31963256884173 A 13 13 0 0 1 37.22463159281663 47.593766000418704 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 17.57021865868836 43.891867539524405 A 33 33 0 0 0 17.045225353099063 48.272913443982866 L 22.038373026871934 48.53459322519758 A 28 28 0 0 1 22.48382189222043 44.817342154747976 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 12.656615425156296 42.96639292430083 A 38 38 0 0 0 12.052077679326196 48.01123366276814 L 17.045225353099063 48.272913443982866 A 33 33 0 0 1 17.57021865868836 43.891867539524405 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 38.42982302564229 36.211200023858396 A 18 18 0 0 0 32.31102835928456 46.66829138519513 L 37.22463159281663 47.593766000418704 A 13 13 0 0 1 41.64376107407499 40.04142223945328 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 35.21588497720959 32.38097780826351 A 23 23 0 0 0 27.397425125752495 45.742816769971554 L 32.31102835928456 46.66829138519513 A 18 18 0 0 1 38.42982302564229 36.211200023858396 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 28.788008880344197 24.72053337707373 A 33 33 0 0 0 17.57021865868836 43.891867539524405 L 22.48382189222043 44.817342154747976 A 28 28 0 0 1 32.001946928776896 28.55075559266862 Z" stroke="none" stroke-width="0.1" fill="black"/>
<path d="M 25.5740708319115 20.89031116147884 A 38 38 0 0 0 12.656615425156296 42.96639292430083 L 17.57021865868836 43.891867539524405 A 33 33 0 0 1 28.788008880344197 24.72053337707373 Z" stroke="none" stroke-width="0.1" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,112 @@
import math
class SVGGenerator:
"""SVGを作るためのクラス"""
def __init__(self, width_mm=100, height_mm=100):
self.width_mm = width_mm
self.height_mm = height_mm
self.elements = []
def circle(self, radius_mm):
"""半径radius_mmのなるべく細い円を書く"""
self.elements.append(
f'<circle cx="{self.width_mm / 2}" cy="{self.height_mm / 2}" r="{radius_mm}" stroke="black" stroke-width="0.1" fill="none" />'
)
def sector_ring(self, outer_radius_mm, inner_radius_mm, start_degree, end_degree):
"""outer_radius_mmの円から、inner_radius_mmの円を切り取り、start_degreeからend_degreeまでを取り出した扇形環を描く"""
def polar_to_cartesian(radius, angle_in_degrees):
angle_in_radians = (angle_in_degrees - 90) * math.pi / 180.0
return (
(self.width_mm / 2) + (radius * math.cos(angle_in_radians)),
(self.height_mm / 2) + (radius * math.sin(angle_in_radians))
)
start_outer = polar_to_cartesian(outer_radius_mm, end_degree)
end_outer = polar_to_cartesian(outer_radius_mm, start_degree)
start_inner = polar_to_cartesian(inner_radius_mm, end_degree)
end_inner = polar_to_cartesian(inner_radius_mm, start_degree)
large_arc_flag = "1" if end_degree - start_degree > 180 else "0"
path_data = [
f"M {start_outer[0]} {start_outer[1]}",
f"A {outer_radius_mm} {outer_radius_mm} 0 {large_arc_flag} 0 {end_outer[0]} {end_outer[1]}",
f"L {end_inner[0]} {end_inner[1]}",
f"A {inner_radius_mm} {inner_radius_mm} 0 {large_arc_flag} 1 {start_inner[0]} {start_inner[1]}",
"Z"
]
self.elements.append(f'<path d="{" ".join(path_data)}" stroke="none" stroke-width="0.1" fill="black"/>')
def indicator(self, inner_radius_mm, pos_digree):
"""inner_radius_mm、0度の位置に大きさ2mm程度の三角を描く"""
size_mm = 2
def polar_to_cartesian(radius, angle_in_degrees):
angle_in_radians = (angle_in_degrees - 90) * math.pi / 180.0
return (
(self.width_mm / 2) + (radius * math.cos(angle_in_radians)),
(self.height_mm / 2) + (radius * math.sin(angle_in_radians))
)
# Tip of the triangle, pointing towards the center
p1 = polar_to_cartesian(inner_radius_mm, 0 + pos_digree)
# Base of the triangle is further out
base_radius = inner_radius_mm + size_mm
half_base_width = size_mm / 2
half_base_angle_rad = half_base_width / base_radius
half_base_angle_deg = half_base_angle_rad * 180 / math.pi
p2 = polar_to_cartesian(base_radius, -half_base_angle_deg + pos_digree)
p3 = polar_to_cartesian(base_radius, half_base_angle_deg + pos_digree)
self.elements.append(
f'<polygon points="{p1[0]},{p1[1]} {p2[0]},{p2[1]} {p3[0]},{p3[1]}" fill="black" />'
)
def export_to_svg(self):
"""描いたデータをsvgにして出力する。"""
svg_elements = "\n".join(self.elements)
return (
f'<svg width="{self.width_mm}mm" height="{self.height_mm}mm" viewBox="0 0 {self.width_mm} {self.height_mm}" xmlns="http://www.w3.org/2000/svg">\n'
+ svg_elements
+ "\n</svg>"
)
encoders = [
{
"name": "one_dial",
"bits": 6,
"degrees": [
0, # 最初の遊び
]+[
(i * 23 / 3 + 20) for i in range(33+2)
] + [320],
"indicator": 23/3*.5+20 + 23/3+5,
}
]
for encoder in encoders:
gen = SVGGenerator()
bits = encoder["bits"]
templ = encoder["degrees"]
gen.circle(8)
gen.circle(8+5*bits)
gen.indicator(8+5*bits, encoder["indicator"])
for i in range(len(templ)-1):
num = i+1
gray = num ^ (num>>1)
s = f'{gray:0{bits}b}'
# print(s)
for j in range(bits):
if (s[bits-j-1] == "1"):
gen.sector_ring(8+5+5*j, 8+5*j, templ[i], templ[i+1])
with open(f"{encoder["name"]}.svg", "w") as f:
f.write(gen.export_to_svg())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff