├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── docs ├── certification-mark-US002561.png ├── cover.scad ├── cover.stl ├── ibom.html ├── upico.d2 ├── upico.jpg ├── upico.pdf └── upico_hld.png ├── dts └── r-01 │ └── uc_board.dts ├── extender ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml ├── memory.x └── src │ ├── main.rs │ └── upico.rs ├── install.sh ├── pcb ├── USON-8_2x3mm_P0.5mm.kicad_mod ├── USON-8_4x3mm_P0.8mm.kicad_mod ├── ext.kicad_mod ├── fp-lib-table ├── speaker.kicad_mod ├── sym-lib-table ├── upico.kicad_pcb ├── upico.kicad_prl ├── upico.kicad_pro ├── upico.kicad_sch └── upico.kicad_sym ├── readme.md ├── rust-toolchain ├── src ├── config.rs ├── extender.rs ├── gpio.rs ├── main.rs ├── resources │ ├── extender.uf2 │ ├── pinout.ansi │ └── pinout_full.ansi └── service.rs └── upico.service /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: μPico 4 | 5 | jobs: 6 | lints: 7 | name: Lints 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | - name: Install stable toolchain 13 | uses: actions-rs/toolchain@v1 14 | with: 15 | profile: minimal 16 | toolchain: stable 17 | override: true 18 | components: rustfmt, clippy 19 | - name: Run cargo fmt 20 | uses: actions-rs/cargo@v1 21 | with: 22 | command: fmt 23 | args: --all -- --check 24 | - name: Run cargo clippy 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: clippy 28 | args: -- -D warnings 29 | build: 30 | name: Build 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout sources 34 | uses: actions/checkout@v2 35 | - name: Install stable toolchain 36 | uses: actions-rs/toolchain@v1 37 | with: 38 | profile: minimal 39 | toolchain: stable 40 | override: true 41 | - name: Install cross 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: install 45 | args: --git https://github.com/cross-rs/cross cross 46 | - name: R-01 Build 47 | run: cross build --release --target=riscv64gc-unknown-linux-gnu --no-default-features --features r01 48 | - name: Copy R-01 binary 49 | run : cp -f ./target/riscv64gc-unknown-linux-gnu/release/upico upico 50 | - name: Compress R-01 Build 51 | uses: a7ul/tar-action@v1.1.0 52 | with: 53 | command: c 54 | files: | 55 | ./readme.md 56 | ./LICENSE-MIT 57 | ./LICENSE-APACHE 58 | ./install.sh 59 | ./upico.service 60 | ./upico 61 | outPath: upico_${{ github.ref_name }}.r01.tar.gz 62 | - name: CM4 Build 63 | run: cross build --release --target=armv7-unknown-linux-musleabihf --no-default-features --features cm4 64 | - name: Copy CM4 binary 65 | run : cp -f ./target/armv7-unknown-linux-musleabihf/release/upico upico 66 | - name: Compress CM4 Build 67 | uses: a7ul/tar-action@v1.1.0 68 | with: 69 | command: c 70 | files: | 71 | ./readme.md 72 | ./LICENSE-MIT 73 | ./LICENSE-APACHE 74 | ./install.sh 75 | ./upico.service 76 | ./upico 77 | outPath: upico_${{ github.ref_name }}.cm4.tar.gz 78 | - name: CM4 Bookworm Build 79 | run: cross build --release --target=armv7-unknown-linux-musleabihf --no-default-features --features cm4-bookworm 80 | - name: Copy CM4 Bookworm binary 81 | run : cp -f ./target/armv7-unknown-linux-musleabihf/release/upico upico 82 | - name: Compress CM4 Bookworm Build 83 | uses: a7ul/tar-action@v1.1.0 84 | with: 85 | command: c 86 | files: | 87 | ./readme.md 88 | ./LICENSE-MIT 89 | ./LICENSE-APACHE 90 | ./install.sh 91 | ./upico.service 92 | ./upico 93 | outPath: upico_${{ github.ref_name }}.cm4-bookworm.tar.gz 94 | - name: A06 Build 95 | run: cross build --release --target=armv7-unknown-linux-musleabihf --no-default-features --features a06 96 | - name: Copy A06 binary 97 | run : cp -f ./target/armv7-unknown-linux-musleabihf/release/upico upico 98 | - name: Compress A06 Build 99 | uses: a7ul/tar-action@v1.1.0 100 | with: 101 | command: c 102 | files: | 103 | ./readme.md 104 | ./LICENSE-MIT 105 | ./LICENSE-APACHE 106 | ./install.sh 107 | ./upico.service 108 | ./upico 109 | outPath: upico_${{ github.ref_name }}.a06.tar.gz 110 | - name: Upload Build Artifacts 111 | uses: actions/upload-artifact@v3 112 | with: 113 | name: upico-installers 114 | path: upico_*.tar.gz 115 | retention-days: 7 116 | - name: Release 117 | uses: softprops/action-gh-release@v1 118 | if: startsWith(github.ref, 'refs/tags/') 119 | with: 120 | files: | 121 | upico_${{ github.ref_name }}.r01.tar.gz 122 | upico_${{ github.ref_name }}.cm4.tar.gz 123 | upico_${{ github.ref_name }}.cm4-bookworm.tar.gz 124 | upico_${{ github.ref_name }}.a06.tar.gz 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /extender/target 3 | fp-info-cache 4 | upico-backups 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pcb/jb-lib"] 2 | path = pcb/jb-lib 3 | url = git@github.com:dotcypress/kicad-lib.git 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.check.allTargets": false, 3 | "rust-analyzer.linkedProjects": [ 4 | "./Cargo.toml", 5 | "./extender/Cargo.toml" 6 | ] 7 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | dotcypress@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.13" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "utf8parse", 17 | ] 18 | 19 | [[package]] 20 | name = "anstyle" 21 | version = "1.0.6" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 24 | 25 | [[package]] 26 | name = "anstyle-parse" 27 | version = "0.2.3" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 30 | dependencies = [ 31 | "utf8parse", 32 | ] 33 | 34 | [[package]] 35 | name = "anstyle-query" 36 | version = "1.0.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 39 | dependencies = [ 40 | "windows-sys", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle-wincon" 45 | version = "3.0.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 48 | dependencies = [ 49 | "anstyle", 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "autocfg" 55 | version = "1.2.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 58 | 59 | [[package]] 60 | name = "byteorder" 61 | version = "1.5.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 64 | 65 | [[package]] 66 | name = "cc" 67 | version = "1.0.94" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" 70 | 71 | [[package]] 72 | name = "clap" 73 | version = "4.5.4" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 76 | dependencies = [ 77 | "clap_builder", 78 | ] 79 | 80 | [[package]] 81 | name = "clap_builder" 82 | version = "4.5.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 85 | dependencies = [ 86 | "anstream", 87 | "anstyle", 88 | "clap_lex", 89 | "strsim", 90 | ] 91 | 92 | [[package]] 93 | name = "clap_complete" 94 | version = "4.5.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" 97 | dependencies = [ 98 | "clap", 99 | ] 100 | 101 | [[package]] 102 | name = "clap_lex" 103 | version = "0.7.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 106 | 107 | [[package]] 108 | name = "colorchoice" 109 | version = "1.0.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 112 | 113 | [[package]] 114 | name = "libc" 115 | version = "0.2.153" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 118 | 119 | [[package]] 120 | name = "libusb1-sys" 121 | version = "0.6.4" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "f9d0e2afce4245f2c9a418511e5af8718bcaf2fa408aefb259504d1a9cb25f27" 124 | dependencies = [ 125 | "cc", 126 | "libc", 127 | "pkg-config", 128 | "vcpkg", 129 | ] 130 | 131 | [[package]] 132 | name = "num-traits" 133 | version = "0.2.18" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 136 | dependencies = [ 137 | "autocfg", 138 | ] 139 | 140 | [[package]] 141 | name = "paste" 142 | version = "1.0.14" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 145 | 146 | [[package]] 147 | name = "pkg-config" 148 | version = "0.3.30" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 151 | 152 | [[package]] 153 | name = "proc-macro2" 154 | version = "1.0.79" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 157 | dependencies = [ 158 | "unicode-ident", 159 | ] 160 | 161 | [[package]] 162 | name = "quote" 163 | version = "1.0.36" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 166 | dependencies = [ 167 | "proc-macro2", 168 | ] 169 | 170 | [[package]] 171 | name = "rmp" 172 | version = "0.8.12" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" 175 | dependencies = [ 176 | "byteorder", 177 | "num-traits", 178 | "paste", 179 | ] 180 | 181 | [[package]] 182 | name = "rmp-serde" 183 | version = "1.1.2" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" 186 | dependencies = [ 187 | "byteorder", 188 | "rmp", 189 | "serde", 190 | ] 191 | 192 | [[package]] 193 | name = "rusb" 194 | version = "0.9.3" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "45fff149b6033f25e825cbb7b2c625a11ee8e6dac09264d49beb125e39aa97bf" 197 | dependencies = [ 198 | "libc", 199 | "libusb1-sys", 200 | ] 201 | 202 | [[package]] 203 | name = "serde" 204 | version = "1.0.197" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 207 | dependencies = [ 208 | "serde_derive", 209 | ] 210 | 211 | [[package]] 212 | name = "serde_derive" 213 | version = "1.0.197" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 216 | dependencies = [ 217 | "proc-macro2", 218 | "quote", 219 | "syn", 220 | ] 221 | 222 | [[package]] 223 | name = "strsim" 224 | version = "0.11.1" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 227 | 228 | [[package]] 229 | name = "syn" 230 | version = "2.0.58" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" 233 | dependencies = [ 234 | "proc-macro2", 235 | "quote", 236 | "unicode-ident", 237 | ] 238 | 239 | [[package]] 240 | name = "unicode-ident" 241 | version = "1.0.12" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 244 | 245 | [[package]] 246 | name = "upico" 247 | version = "0.2.1" 248 | dependencies = [ 249 | "clap", 250 | "clap_complete", 251 | "rmp-serde", 252 | "rusb", 253 | "serde", 254 | ] 255 | 256 | [[package]] 257 | name = "utf8parse" 258 | version = "0.2.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 261 | 262 | [[package]] 263 | name = "vcpkg" 264 | version = "0.2.15" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 267 | 268 | [[package]] 269 | name = "windows-sys" 270 | version = "0.52.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 273 | dependencies = [ 274 | "windows-targets", 275 | ] 276 | 277 | [[package]] 278 | name = "windows-targets" 279 | version = "0.52.5" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 282 | dependencies = [ 283 | "windows_aarch64_gnullvm", 284 | "windows_aarch64_msvc", 285 | "windows_i686_gnu", 286 | "windows_i686_gnullvm", 287 | "windows_i686_msvc", 288 | "windows_x86_64_gnu", 289 | "windows_x86_64_gnullvm", 290 | "windows_x86_64_msvc", 291 | ] 292 | 293 | [[package]] 294 | name = "windows_aarch64_gnullvm" 295 | version = "0.52.5" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 298 | 299 | [[package]] 300 | name = "windows_aarch64_msvc" 301 | version = "0.52.5" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 304 | 305 | [[package]] 306 | name = "windows_i686_gnu" 307 | version = "0.52.5" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 310 | 311 | [[package]] 312 | name = "windows_i686_gnullvm" 313 | version = "0.52.5" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 316 | 317 | [[package]] 318 | name = "windows_i686_msvc" 319 | version = "0.52.5" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 322 | 323 | [[package]] 324 | name = "windows_x86_64_gnu" 325 | version = "0.52.5" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 328 | 329 | [[package]] 330 | name = "windows_x86_64_gnullvm" 331 | version = "0.52.5" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 334 | 335 | [[package]] 336 | name = "windows_x86_64_msvc" 337 | version = "0.52.5" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 340 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "upico" 3 | version = "0.2.1" 4 | authors = ["Vitaly Domnikov "] 5 | repository = "https://github.com/dotcypress/upico" 6 | description = "uPico control app" 7 | license = "MIT/Apache-2.0" 8 | readme = "README.md" 9 | edition = "2021" 10 | 11 | [dependencies] 12 | clap = "4.4.6" 13 | clap_complete = "4.5.2" 14 | rmp-serde = "1.1.2" 15 | rusb = "0.9" 16 | serde = { version = "1.0.188", features = ["serde_derive"] } 17 | 18 | [features] 19 | default = ["cm4"] 20 | a04 = [] 21 | a06 = [] 22 | cm4 = [] 23 | cm4-bookworm = [] 24 | r01 = [] 25 | 26 | [profile.release] 27 | strip = true 28 | 29 | [package.metadata.cross.target.riscv64gc-unknown-linux-gnu] 30 | image = "ghcr.io/cross-rs/riscv64gc-unknown-linux-gnu:0.2.4" 31 | 32 | [package.metadata.cross.target.armv7-unknown-linux-musleabihf] 33 | image = "ghcr.io/cross-rs/armv7-unknown-linux-musleabihf:0.2.4" 34 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2023-2024 Vitaly Domnikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/certification-mark-US002561.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcypress/upico/9b27084063b062241dfb2fffc1fdef4a609679d1/docs/certification-mark-US002561.png -------------------------------------------------------------------------------- /docs/cover.scad: -------------------------------------------------------------------------------- 1 | $fn=128; 2 | epsilon=0.01; 3 | 4 | module cover() { 5 | difference() { 6 | union() { 7 | cube(size=[58, 9.5, 1], center=true); 8 | translate([0, 0, 1]) 9 | cube(size=[55, 9.5, 1.8], center=true); 10 | } 11 | 12 | translate([5.3+epsilon, 2.5+epsilon, 0]) 13 | cube(size=[44, 5, 20], center=true); 14 | 15 | translate([-24.5, 1.8, 0]) hull() { 16 | cylinder(r=1.8, h=20, center=true); 17 | translate([10, 0, 0]) 18 | cylinder(r=1.8, h=20, center=true); 19 | } 20 | } 21 | } 22 | 23 | module print() { 24 | translate([0, 4, 0.5]) 25 | cube(size=[1, 88, 1], center=true); 26 | for (i=[-3:4]) { 27 | translate([0, i*12, 0.6]) cover(); 28 | } 29 | } 30 | 31 | // print(); 32 | cover(); -------------------------------------------------------------------------------- /docs/cover.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcypress/upico/9b27084063b062241dfb2fffc1fdef4a609679d1/docs/cover.stl -------------------------------------------------------------------------------- /docs/upico.d2: -------------------------------------------------------------------------------- 1 | uConsoleEXT: { 2 | Speakers 3 | USB3 4 | GPIO 5 | USB4 6 | } 7 | 8 | uConsoleEXT.Speakers -> Speakers 9 | 10 | uConsoleEXT.USB4 -> RP2040 11 | uConsoleEXT.USB3 -> USB-C Port 12 | 13 | uConsoleEXT.GPIO -> Pico Power Switch 14 | uConsoleEXT.GPIO -> USB Power Switch 15 | uConsoleEXT.GPIO -> AUX Power Switch 16 | 17 | Pico Power Switch -> RP2040 18 | Pico Power Switch -> PMOD Connector: 3.3V 19 | AUX Power Switch -> PMOD Connector: 5V 20 | USB Power Switch -> USB-C Port 21 | 22 | uConsoleEXT.GPIO -> RP2040: Reset, Boot 23 | 24 | RP2040 -> PMOD Connector: IO 25 | RP2040 -> LED: GPIO25 26 | -------------------------------------------------------------------------------- /docs/upico.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcypress/upico/9b27084063b062241dfb2fffc1fdef4a609679d1/docs/upico.jpg -------------------------------------------------------------------------------- /docs/upico.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcypress/upico/9b27084063b062241dfb2fffc1fdef4a609679d1/docs/upico.pdf -------------------------------------------------------------------------------- /docs/upico_hld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcypress/upico/9b27084063b062241dfb2fffc1fdef4a609679d1/docs/upico_hld.png -------------------------------------------------------------------------------- /extender/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv6m-none-eabi] 2 | # runner = "elf2uf2-rs -d" 3 | runner = "probe-run --chip RP2040" 4 | 5 | rustflags = [ 6 | "-C", "link-arg=--nmagic", 7 | "-C", "link-arg=-Tlink.x", 8 | "-C", "link-arg=-Tdefmt.x", 9 | "-C", "inline-threshold=5", 10 | "-C", "no-vectorize-loops", 11 | ] 12 | 13 | [build] 14 | target = "thumbv6m-none-eabi" -------------------------------------------------------------------------------- /extender/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "arrayvec" 7 | version = "0.7.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" 10 | 11 | [[package]] 12 | name = "atomic-polyfill" 13 | version = "1.0.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" 16 | dependencies = [ 17 | "critical-section", 18 | ] 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.2.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 25 | 26 | [[package]] 27 | name = "bare-metal" 28 | version = "0.2.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 31 | dependencies = [ 32 | "rustc_version 0.2.3", 33 | ] 34 | 35 | [[package]] 36 | name = "bare-metal" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" 40 | 41 | [[package]] 42 | name = "bitfield" 43 | version = "0.13.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "1.3.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 52 | 53 | [[package]] 54 | name = "byteorder" 55 | version = "1.4.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 58 | 59 | [[package]] 60 | name = "cortex-m" 61 | version = "0.7.7" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" 64 | dependencies = [ 65 | "bare-metal 0.2.5", 66 | "bitfield", 67 | "embedded-hal", 68 | "volatile-register", 69 | ] 70 | 71 | [[package]] 72 | name = "cortex-m-rt" 73 | version = "0.7.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" 76 | dependencies = [ 77 | "cortex-m-rt-macros", 78 | ] 79 | 80 | [[package]] 81 | name = "cortex-m-rt-macros" 82 | version = "0.7.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" 85 | dependencies = [ 86 | "proc-macro2", 87 | "quote", 88 | "syn 1.0.109", 89 | ] 90 | 91 | [[package]] 92 | name = "cortex-m-rtic" 93 | version = "1.1.4" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "d696ae7390bdb9f7978f71ca7144256a2c4616240a6df9002da3c451f9fc8f02" 96 | dependencies = [ 97 | "bare-metal 1.0.0", 98 | "cortex-m", 99 | "cortex-m-rtic-macros", 100 | "heapless", 101 | "rtic-core", 102 | "rtic-monotonic", 103 | "version_check", 104 | ] 105 | 106 | [[package]] 107 | name = "cortex-m-rtic-macros" 108 | version = "1.1.6" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "eefb40b1ca901c759d29526e5c8a0a1b246c20caaa5b4cc5d0f0b94debecd4c7" 111 | dependencies = [ 112 | "proc-macro-error", 113 | "proc-macro2", 114 | "quote", 115 | "rtic-syntax", 116 | "syn 1.0.109", 117 | ] 118 | 119 | [[package]] 120 | name = "crc-any" 121 | version = "2.4.4" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "c01a5e1f881f6fb6099a7bdf949e946719fd4f1fefa56264890574febf0eb6d0" 124 | dependencies = [ 125 | "debug-helper", 126 | ] 127 | 128 | [[package]] 129 | name = "critical-section" 130 | version = "1.1.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" 133 | 134 | [[package]] 135 | name = "debug-helper" 136 | version = "0.3.13" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" 139 | 140 | [[package]] 141 | name = "defmt" 142 | version = "0.3.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "d3a0ae7494d9bff013d7b89471f4c424356a71e9752e0c78abe7e6c608a16bb3" 145 | dependencies = [ 146 | "bitflags", 147 | "defmt-macros", 148 | ] 149 | 150 | [[package]] 151 | name = "defmt-macros" 152 | version = "0.3.7" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "18bdc7a7b92ac413e19e95240e75d3a73a8d8e78aa24a594c22cbb4d44b4bbda" 155 | dependencies = [ 156 | "defmt-parser", 157 | "proc-macro-error", 158 | "proc-macro2", 159 | "quote", 160 | "syn 2.0.58", 161 | ] 162 | 163 | [[package]] 164 | name = "defmt-parser" 165 | version = "0.3.4" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" 168 | dependencies = [ 169 | "thiserror", 170 | ] 171 | 172 | [[package]] 173 | name = "defmt-rtt" 174 | version = "0.4.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "609923761264dd99ed9c7d209718cda4631c5fe84668e0f0960124cbb844c49f" 177 | dependencies = [ 178 | "critical-section", 179 | "defmt", 180 | ] 181 | 182 | [[package]] 183 | name = "either" 184 | version = "1.11.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" 187 | 188 | [[package]] 189 | name = "embedded-dma" 190 | version = "0.2.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" 193 | dependencies = [ 194 | "stable_deref_trait", 195 | ] 196 | 197 | [[package]] 198 | name = "embedded-hal" 199 | version = "0.2.7" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 202 | dependencies = [ 203 | "nb 0.1.3", 204 | "void", 205 | ] 206 | 207 | [[package]] 208 | name = "encode_unicode" 209 | version = "0.3.6" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 212 | 213 | [[package]] 214 | name = "frunk" 215 | version = "0.4.2" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "11a351b59e12f97b4176ee78497dff72e4276fb1ceb13e19056aca7fa0206287" 218 | dependencies = [ 219 | "frunk_core", 220 | "frunk_derives", 221 | ] 222 | 223 | [[package]] 224 | name = "frunk_core" 225 | version = "0.4.2" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "af2469fab0bd07e64ccf0ad57a1438f63160c69b2e57f04a439653d68eb558d6" 228 | 229 | [[package]] 230 | name = "frunk_derives" 231 | version = "0.4.2" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e" 234 | dependencies = [ 235 | "frunk_proc_macro_helpers", 236 | "quote", 237 | "syn 2.0.58", 238 | ] 239 | 240 | [[package]] 241 | name = "frunk_proc_macro_helpers" 242 | version = "0.1.2" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "35b54add839292b743aeda6ebedbd8b11e93404f902c56223e51b9ec18a13d2c" 245 | dependencies = [ 246 | "frunk_core", 247 | "proc-macro2", 248 | "quote", 249 | "syn 2.0.58", 250 | ] 251 | 252 | [[package]] 253 | name = "fugit" 254 | version = "0.3.7" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" 257 | dependencies = [ 258 | "gcd", 259 | ] 260 | 261 | [[package]] 262 | name = "gcd" 263 | version = "2.3.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" 266 | 267 | [[package]] 268 | name = "hash32" 269 | version = "0.2.1" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" 272 | dependencies = [ 273 | "byteorder", 274 | ] 275 | 276 | [[package]] 277 | name = "hashbrown" 278 | version = "0.12.3" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 281 | 282 | [[package]] 283 | name = "heapless" 284 | version = "0.7.17" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" 287 | dependencies = [ 288 | "atomic-polyfill", 289 | "hash32", 290 | "rustc_version 0.4.0", 291 | "spin", 292 | "stable_deref_trait", 293 | ] 294 | 295 | [[package]] 296 | name = "indexmap" 297 | version = "1.9.3" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 300 | dependencies = [ 301 | "autocfg", 302 | "hashbrown", 303 | ] 304 | 305 | [[package]] 306 | name = "itertools" 307 | version = "0.10.5" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 310 | dependencies = [ 311 | "either", 312 | ] 313 | 314 | [[package]] 315 | name = "lock_api" 316 | version = "0.4.11" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 319 | dependencies = [ 320 | "autocfg", 321 | "scopeguard", 322 | ] 323 | 324 | [[package]] 325 | name = "nb" 326 | version = "0.1.3" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 329 | dependencies = [ 330 | "nb 1.1.0", 331 | ] 332 | 333 | [[package]] 334 | name = "nb" 335 | version = "1.1.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 338 | 339 | [[package]] 340 | name = "num_enum" 341 | version = "0.5.11" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" 344 | dependencies = [ 345 | "num_enum_derive", 346 | ] 347 | 348 | [[package]] 349 | name = "num_enum_derive" 350 | version = "0.5.11" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" 353 | dependencies = [ 354 | "proc-macro2", 355 | "quote", 356 | "syn 1.0.109", 357 | ] 358 | 359 | [[package]] 360 | name = "panic-halt" 361 | version = "0.2.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" 364 | 365 | [[package]] 366 | name = "panic-probe" 367 | version = "0.3.1" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9" 370 | dependencies = [ 371 | "cortex-m", 372 | ] 373 | 374 | [[package]] 375 | name = "paste" 376 | version = "1.0.14" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 379 | 380 | [[package]] 381 | name = "pio" 382 | version = "0.2.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" 385 | dependencies = [ 386 | "arrayvec", 387 | "num_enum", 388 | "paste", 389 | ] 390 | 391 | [[package]] 392 | name = "proc-macro-error" 393 | version = "1.0.4" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 396 | dependencies = [ 397 | "proc-macro-error-attr", 398 | "proc-macro2", 399 | "quote", 400 | "syn 1.0.109", 401 | "version_check", 402 | ] 403 | 404 | [[package]] 405 | name = "proc-macro-error-attr" 406 | version = "1.0.4" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 409 | dependencies = [ 410 | "proc-macro2", 411 | "quote", 412 | "version_check", 413 | ] 414 | 415 | [[package]] 416 | name = "proc-macro2" 417 | version = "1.0.79" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 420 | dependencies = [ 421 | "unicode-ident", 422 | ] 423 | 424 | [[package]] 425 | name = "quote" 426 | version = "1.0.36" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 429 | dependencies = [ 430 | "proc-macro2", 431 | ] 432 | 433 | [[package]] 434 | name = "rand_core" 435 | version = "0.6.4" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 438 | 439 | [[package]] 440 | name = "rp2040-boot2" 441 | version = "0.3.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" 444 | dependencies = [ 445 | "crc-any", 446 | ] 447 | 448 | [[package]] 449 | name = "rp2040-hal" 450 | version = "0.9.2" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "1ff2b9ae7e6dd6720fd9f64250c9087260e50fe98b6b032ccca65be3581167ca" 453 | dependencies = [ 454 | "cortex-m", 455 | "critical-section", 456 | "embedded-dma", 457 | "embedded-hal", 458 | "frunk", 459 | "fugit", 460 | "itertools", 461 | "nb 1.1.0", 462 | "paste", 463 | "pio", 464 | "rand_core", 465 | "rp2040-hal-macros", 466 | "rp2040-pac", 467 | "rtic-monotonic", 468 | "usb-device", 469 | "vcell", 470 | "void", 471 | ] 472 | 473 | [[package]] 474 | name = "rp2040-hal-macros" 475 | version = "0.1.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "86479063e497efe1ae81995ef9071f54fd1c7427e04d6c5b84cde545ff672a5e" 478 | dependencies = [ 479 | "cortex-m-rt", 480 | "proc-macro2", 481 | "quote", 482 | "syn 1.0.109", 483 | ] 484 | 485 | [[package]] 486 | name = "rp2040-pac" 487 | version = "0.5.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "12d9d8375815f543f54835d01160d4e47f9e2cae75f17ff8f1ec19ce1da96e4c" 490 | dependencies = [ 491 | "cortex-m", 492 | "cortex-m-rt", 493 | "critical-section", 494 | "vcell", 495 | ] 496 | 497 | [[package]] 498 | name = "rtic-core" 499 | version = "1.0.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42" 502 | 503 | [[package]] 504 | name = "rtic-monotonic" 505 | version = "1.0.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "fb8b0b822d1a366470b9cea83a1d4e788392db763539dc4ba022bcc787fece82" 508 | 509 | [[package]] 510 | name = "rtic-syntax" 511 | version = "1.0.3" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "5f5e215601dc467752c2bddc6284a622c6f3d2bab569d992adcd5ab7e4cb9478" 514 | dependencies = [ 515 | "indexmap", 516 | "proc-macro2", 517 | "quote", 518 | "syn 1.0.109", 519 | ] 520 | 521 | [[package]] 522 | name = "rustc_version" 523 | version = "0.2.3" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 526 | dependencies = [ 527 | "semver 0.9.0", 528 | ] 529 | 530 | [[package]] 531 | name = "rustc_version" 532 | version = "0.4.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 535 | dependencies = [ 536 | "semver 1.0.22", 537 | ] 538 | 539 | [[package]] 540 | name = "scopeguard" 541 | version = "1.2.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 544 | 545 | [[package]] 546 | name = "semver" 547 | version = "0.9.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 550 | dependencies = [ 551 | "semver-parser", 552 | ] 553 | 554 | [[package]] 555 | name = "semver" 556 | version = "1.0.22" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" 559 | 560 | [[package]] 561 | name = "semver-parser" 562 | version = "0.7.0" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 565 | 566 | [[package]] 567 | name = "serde" 568 | version = "1.0.197" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 571 | dependencies = [ 572 | "serde_derive", 573 | ] 574 | 575 | [[package]] 576 | name = "serde_derive" 577 | version = "1.0.197" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 580 | dependencies = [ 581 | "proc-macro2", 582 | "quote", 583 | "syn 2.0.58", 584 | ] 585 | 586 | [[package]] 587 | name = "spin" 588 | version = "0.9.8" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 591 | dependencies = [ 592 | "lock_api", 593 | ] 594 | 595 | [[package]] 596 | name = "ssmarshal" 597 | version = "1.0.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" 600 | dependencies = [ 601 | "encode_unicode", 602 | "serde", 603 | ] 604 | 605 | [[package]] 606 | name = "stable_deref_trait" 607 | version = "1.2.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 610 | 611 | [[package]] 612 | name = "syn" 613 | version = "1.0.109" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 616 | dependencies = [ 617 | "proc-macro2", 618 | "quote", 619 | "unicode-ident", 620 | ] 621 | 622 | [[package]] 623 | name = "syn" 624 | version = "2.0.58" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" 627 | dependencies = [ 628 | "proc-macro2", 629 | "quote", 630 | "unicode-ident", 631 | ] 632 | 633 | [[package]] 634 | name = "thiserror" 635 | version = "1.0.58" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" 638 | dependencies = [ 639 | "thiserror-impl", 640 | ] 641 | 642 | [[package]] 643 | name = "thiserror-impl" 644 | version = "1.0.58" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" 647 | dependencies = [ 648 | "proc-macro2", 649 | "quote", 650 | "syn 2.0.58", 651 | ] 652 | 653 | [[package]] 654 | name = "unicode-ident" 655 | version = "1.0.12" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 658 | 659 | [[package]] 660 | name = "upico-extender" 661 | version = "0.0.1" 662 | dependencies = [ 663 | "cortex-m", 664 | "cortex-m-rtic", 665 | "defmt", 666 | "defmt-rtt", 667 | "embedded-hal", 668 | "fugit", 669 | "heapless", 670 | "nb 1.1.0", 671 | "panic-halt", 672 | "panic-probe", 673 | "pio", 674 | "rp2040-boot2", 675 | "rp2040-hal", 676 | "usb-device", 677 | "usbd-hid", 678 | "usbd-serial", 679 | ] 680 | 681 | [[package]] 682 | name = "usb-device" 683 | version = "0.2.9" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" 686 | 687 | [[package]] 688 | name = "usbd-hid" 689 | version = "0.6.1" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132" 692 | dependencies = [ 693 | "serde", 694 | "ssmarshal", 695 | "usb-device", 696 | "usbd-hid-macros", 697 | ] 698 | 699 | [[package]] 700 | name = "usbd-hid-descriptors" 701 | version = "0.1.2" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1" 704 | dependencies = [ 705 | "bitfield", 706 | ] 707 | 708 | [[package]] 709 | name = "usbd-hid-macros" 710 | version = "0.6.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2" 713 | dependencies = [ 714 | "byteorder", 715 | "proc-macro2", 716 | "quote", 717 | "serde", 718 | "syn 1.0.109", 719 | "usbd-hid-descriptors", 720 | ] 721 | 722 | [[package]] 723 | name = "usbd-serial" 724 | version = "0.1.1" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "db75519b86287f12dcf0d171c7cf4ecc839149fe9f3b720ac4cfce52959e1dfe" 727 | dependencies = [ 728 | "embedded-hal", 729 | "nb 0.1.3", 730 | "usb-device", 731 | ] 732 | 733 | [[package]] 734 | name = "vcell" 735 | version = "0.1.3" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" 738 | 739 | [[package]] 740 | name = "version_check" 741 | version = "0.9.4" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 744 | 745 | [[package]] 746 | name = "void" 747 | version = "1.0.2" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 750 | 751 | [[package]] 752 | name = "volatile-register" 753 | version = "0.2.2" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" 756 | dependencies = [ 757 | "vcell", 758 | ] 759 | -------------------------------------------------------------------------------- /extender/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "upico-extender" 3 | version = "0.0.1" 4 | authors = ["Vitaly Domnikov "] 5 | repository = "https://github.com/dotcypress/upico" 6 | description = "uPico GPIO extender firmware" 7 | edition = "2021" 8 | license = "MIT/Apache-2.0" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | cortex-m-rtic = "1.1.4" 13 | defmt = "=0.3.2" 14 | defmt-rtt = "0.4.0" 15 | panic-halt = "0.2.0" 16 | rp2040-hal = { version = "0.9.1", features = ["rt", "critical-section-impl", "rtic-monotonic"] } 17 | rp2040-boot2 = "0.3.0" 18 | pio = "0.2.1" 19 | usbd-serial = "0.1.1" 20 | usbd-hid = "0.6.1" 21 | usb-device = "0.2.9" 22 | cortex-m = "0.7.7" 23 | panic-probe = "0.3.1" 24 | embedded-hal = "0.2.7" 25 | fugit = "0.3.7" 26 | heapless = "0.7.16" 27 | nb = "1.1.0" 28 | 29 | [profile.dev] 30 | codegen-units = 1 31 | debug = 2 32 | debug-assertions = true 33 | incremental = false 34 | opt-level = 3 35 | overflow-checks = true 36 | 37 | [profile.release] 38 | codegen-units = 2 39 | debug = 1 40 | debug-assertions = false 41 | incremental = false 42 | lto = 'fat' 43 | opt-level = 3 44 | overflow-checks = false 45 | -------------------------------------------------------------------------------- /extender/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 3 | FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 4 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 5 | } 6 | 7 | EXTERN(BOOT2_FIRMWARE) 8 | 9 | SECTIONS { 10 | /* ### Boot loader */ 11 | .boot2 ORIGIN(BOOT2) : 12 | { 13 | KEEP(*(.boot2)); 14 | } > BOOT2 15 | } INSERT BEFORE .text; -------------------------------------------------------------------------------- /extender/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate panic_probe; 5 | extern crate rp2040_hal as hal; 6 | extern crate rtic; 7 | 8 | use defmt_rtt as _; 9 | 10 | mod upico; 11 | 12 | use cortex_m::singleton; 13 | use hal::adc; 14 | use hal::gpio::*; 15 | use hal::pac; 16 | use hal::pio::{PIOBuilder, PIOExt}; 17 | use hal::timer::{monotonic::Monotonic, *}; 18 | use hal::usb::UsbBus; 19 | use pio::Assembler; 20 | use upico::*; 21 | use usb_device::class_prelude::*; 22 | use usb_device::prelude::*; 23 | 24 | #[link_section = ".boot2"] 25 | #[no_mangle] 26 | #[used] 27 | pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; 28 | 29 | pub const XTAL_FREQ_HZ: u32 = 12_000_000_u32; 30 | 31 | #[rtic::app(device = pac, peripherals = true, dispatchers = [SW0_IRQ, SW1_IRQ])] 32 | mod app { 33 | use super::*; 34 | 35 | #[monotonic(binds = TIMER_IRQ_0, default = true)] 36 | type Oracle = Monotonic; 37 | 38 | #[local] 39 | struct Local { 40 | upico: UpicoClass, 41 | usb_dev: UsbDevice<'static, hal::usb::UsbBus>, 42 | } 43 | 44 | #[shared] 45 | struct Shared {} 46 | 47 | #[init] 48 | fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) { 49 | let mut resets = ctx.device.RESETS; 50 | let mut watchdog = hal::Watchdog::new(ctx.device.WATCHDOG); 51 | let clocks = hal::clocks::init_clocks_and_plls( 52 | XTAL_FREQ_HZ, 53 | ctx.device.XOSC, 54 | ctx.device.CLOCKS, 55 | ctx.device.PLL_SYS, 56 | ctx.device.PLL_USB, 57 | &mut resets, 58 | &mut watchdog, 59 | ) 60 | .ok() 61 | .expect("Clocks init failed"); 62 | 63 | let mut timer = hal::Timer::new(ctx.device.TIMER, &mut resets, &clocks); 64 | let alarm = timer.alarm_0().expect("Alarm0 init failed"); 65 | let mono = Monotonic::new(timer, alarm); 66 | 67 | let sio = hal::Sio::new(ctx.device.SIO); 68 | let pins = Pins::new( 69 | ctx.device.IO_BANK0, 70 | ctx.device.PADS_BANK0, 71 | sio.gpio_bank0, 72 | &mut resets, 73 | ); 74 | let led = pins.gpio25.into_push_pull_output(); 75 | let adc = adc::Adc::new(ctx.device.ADC, &mut resets); 76 | let adc_pins = ( 77 | adc::AdcPin::new(pins.gpio26), 78 | adc::AdcPin::new(pins.gpio27), 79 | adc::AdcPin::new(pins.gpio28), 80 | adc::AdcPin::new(pins.gpio29), 81 | ); 82 | 83 | pins.gpio0.into_function::(); 84 | pins.gpio1.into_function::(); 85 | pins.gpio2.into_function::(); 86 | pins.gpio3.into_function::(); 87 | pins.gpio4.into_function::(); 88 | pins.gpio5.into_function::(); 89 | pins.gpio6.into_function::(); 90 | pins.gpio7.into_function::(); 91 | pins.gpio8.into_function::(); 92 | pins.gpio9.into_function::(); 93 | pins.gpio10.into_function::(); 94 | pins.gpio11.into_function::(); 95 | pins.gpio12.into_function::(); 96 | pins.gpio13.into_function::(); 97 | pins.gpio14.into_function::(); 98 | pins.gpio15.into_function::(); 99 | 100 | let mut asm = Assembler::new(); 101 | let mut wrap_target = asm.label(); 102 | let mut wrap_source = asm.label(); 103 | asm.bind(&mut wrap_target); 104 | asm.out(pio::OutDestination::EXEC, 32); 105 | asm.bind(&mut wrap_source); 106 | let (mut pio, sm, _, _, _) = ctx.device.PIO0.split(&mut resets); 107 | let program = pio.install(&asm.assemble_program()).unwrap(); 108 | let (sm, rx, tx) = PIOBuilder::from_program(program) 109 | .autopull(true) 110 | .autopush(true) 111 | .pull_threshold(32) 112 | .push_threshold(32) 113 | .in_pin_base(0) 114 | .out_pins(0, 16) 115 | .build(sm); 116 | sm.start(); 117 | 118 | let usb_regs = ctx.device.USBCTRL_REGS; 119 | let usb_dpram = ctx.device.USBCTRL_DPRAM; 120 | let usb_bus = UsbBus::new(usb_regs, usb_dpram, clocks.usb_clock, true, &mut resets); 121 | let usb_bus: &'static UsbBusAllocator = 122 | singleton!(: UsbBusAllocator = UsbBusAllocator::new(usb_bus)) 123 | .expect("USB init failed"); 124 | 125 | let upico = UpicoClass::new(usb_bus, rx, tx, adc, adc_pins, led); 126 | let usb_dev = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x1209, 0xbc07)) 127 | .manufacturer("vitaly.codes") 128 | .product("uPico GPIO Extender") 129 | .build(); 130 | unsafe { 131 | pac::NVIC::unmask(pac::Interrupt::USBCTRL_IRQ); 132 | }; 133 | 134 | (Shared {}, Local { upico, usb_dev }, init::Monotonics(mono)) 135 | } 136 | 137 | #[task(binds = USBCTRL_IRQ, local = [usb_dev, upico])] 138 | fn usb_irq(ctx: usb_irq::Context) { 139 | ctx.local.usb_dev.poll(&mut [ctx.local.upico]); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /extender/src/upico.rs: -------------------------------------------------------------------------------- 1 | use cortex_m::prelude::_embedded_hal_adc_OneShot; 2 | use rp2040_hal::{ 3 | adc::*, 4 | gpio::{self, bank0::*, *}, 5 | pac::PIO0, 6 | pio, 7 | Adc, 8 | }; 9 | use embedded_hal::digital::v2::OutputPin; 10 | use usb_device::{class_prelude::*, control::*}; 11 | 12 | pub type Led = Pin, hal::gpio::PullDown>; 13 | 14 | pub type AdcPins = ( 15 | AdcPin>, 16 | AdcPin>, 17 | AdcPin>, 18 | AdcPin>, 19 | ); 20 | 21 | pub struct UpicoClass { 22 | adc: Adc, 23 | adc_pins: AdcPins, 24 | iface: InterfaceNumber, 25 | led: Led, 26 | rx: pio::Rx<(PIO0, pio::SM0)>, 27 | tx: pio::Tx<(PIO0, pio::SM0)>, 28 | pin_dirs: u32, 29 | } 30 | 31 | impl UpicoClass { 32 | pub fn new( 33 | alloc: &UsbBusAllocator, 34 | rx: pio::Rx<(PIO0, pio::SM0)>, 35 | tx: pio::Tx<(PIO0, pio::SM0)>, 36 | adc: Adc, 37 | adc_pins: AdcPins, 38 | led: Led, 39 | ) -> UpicoClass { 40 | Self { 41 | adc, 42 | adc_pins, 43 | led, 44 | rx, 45 | tx, 46 | pin_dirs: 0, 47 | iface: alloc.interface(), 48 | } 49 | } 50 | } 51 | 52 | impl UsbClass for UpicoClass { 53 | fn get_configuration_descriptors( 54 | &self, 55 | writer: &mut DescriptorWriter, 56 | ) -> usb_device::Result<()> { 57 | writer.interface(self.iface, 0xff, 0x00, 0x00)?; 58 | Ok(()) 59 | } 60 | 61 | fn control_out(&mut self, xfer: ControlOut) { 62 | let req = xfer.request(); 63 | if req.request_type != RequestType::Vendor || req.recipient != Recipient::Device { 64 | return; 65 | } 66 | match req.request { 67 | 0x00 if xfer.data().len() == 8 => { 68 | let state = u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap()); 69 | self.tx.write(0b01100000_00000000); 70 | self.tx.write(state); 71 | self.pin_dirs = u32::from_le_bytes(xfer.data()[4..8].try_into().unwrap()); 72 | self.tx.write(0b01100000_10000000); 73 | self.tx.write(self.pin_dirs); 74 | xfer.accept() 75 | } 76 | 0x01 => { 77 | self.led.set_state(PinState::from(req.value != 0)).unwrap(); 78 | xfer.accept() 79 | } 80 | _ => xfer.reject(), 81 | } 82 | .ok(); 83 | } 84 | 85 | fn control_in(&mut self, xfer: ControlIn) { 86 | let req = xfer.request(); 87 | if req.request_type != RequestType::Vendor || req.recipient != Recipient::Device { 88 | return; 89 | } 90 | match req.request { 91 | 0x00 => { 92 | self.tx.write(0b01000000_00000000); 93 | if let Some(data) = self.rx.read() { 94 | let mut res = [0; 8]; 95 | res[0..4].copy_from_slice(&data.to_le_bytes()); 96 | res[4..8].copy_from_slice(&self.pin_dirs.to_le_bytes()); 97 | xfer.accept_with(&res) 98 | } else { 99 | xfer.reject() 100 | } 101 | } 102 | 0x01 => { 103 | let ch0: u16 = self.adc.read(&mut self.adc_pins.0).unwrap_or_default(); 104 | let ch1: u16 = self.adc.read(&mut self.adc_pins.1).unwrap_or_default(); 105 | let ch2: u16 = self.adc.read(&mut self.adc_pins.2).unwrap_or_default(); 106 | let ch3: u16 = self.adc.read(&mut self.adc_pins.3).unwrap_or_default(); 107 | 108 | let mut res = [0; 8]; 109 | res[0..2].copy_from_slice(&ch0.to_le_bytes()); 110 | res[2..4].copy_from_slice(&ch1.to_le_bytes()); 111 | res[4..6].copy_from_slice(&ch2.to_le_bytes()); 112 | res[6..8].copy_from_slice(&ch3.to_le_bytes()); 113 | xfer.accept_with(&res) 114 | } 115 | _ => xfer.reject(), 116 | } 117 | .ok(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp -f upico /usr/local/bin/ 4 | chmod +x /usr/local/bin/upico 5 | cp -f upico.service /etc/systemd/system/ 6 | systemctl enable upico 7 | systemctl start upico 8 | echo 'SUBSYSTEM=="usb",ATTRS{idVendor}=="1209",ATTRS{idProduct}=="bc07",MODE="0660",GROUP="plugdev"' > /etc/udev/rules.d/50-upico-permissions.rules 9 | udevadm control --reload-rules 10 | echo "uPico installed" 11 | -------------------------------------------------------------------------------- /pcb/USON-8_2x3mm_P0.5mm.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "USON-8_2x3mm_P0.5mm" (version 20221018) (generator pcbnew) 2 | (layer "F.Cu") 3 | (descr "USON, 8 Pin") 4 | (tags "USON NoLead") 5 | (attr smd) 6 | (fp_text reference "REF**" (at 0.1 -2.1) (layer "F.SilkS") 7 | (effects (font (size 1 1) (thickness 0.15))) 8 | (tstamp 8a39df6d-76d9-4122-8c81-2696dd7633a4) 9 | ) 10 | (fp_text value "USON-8_2x3mm_P0.5mm" (at 0 2.95) (layer "F.Fab") 11 | (effects (font (size 1 1) (thickness 0.15))) 12 | (tstamp 4aad5a3f-f772-458e-a407-a4617f45f594) 13 | ) 14 | (fp_text user "${REFERENCE}" (at 0 -5.08) (layer "F.Fab") 15 | (effects (font (size 1 1) (thickness 0.15))) 16 | (tstamp 902e59f4-0df8-4efd-bac4-afc8858c9d82) 17 | ) 18 | (fp_line (start -1.45 1.15) (end 1.5 1.15) 19 | (stroke (width 0.12) (type solid)) (layer "F.SilkS") (tstamp d5f4d9bd-5eff-4f3d-8261-98e14e534465)) 20 | (fp_line (start -0.9 -1.15) (end 1.45 -1.15) 21 | (stroke (width 0.12) (type solid)) (layer "F.SilkS") (tstamp d97e0ae0-5623-4aee-8eb9-11a7cb19973e)) 22 | (fp_rect (start -1.8 -1.1) (end 1.8 1.1) 23 | (stroke (width 0.05) (type default)) (fill none) (layer "F.CrtYd") (tstamp abfaa82e-8610-4f7f-a3fa-a9a6285b60e3)) 24 | (fp_rect (start -1.5 1) (end 1.5 -1) 25 | (stroke (width 0.05) (type default)) (fill none) (layer "F.Fab") (tstamp 165053ee-e6c9-45de-b508-4e1a2c5fd16a)) 26 | (pad "1" smd roundrect (at -1.4 -0.751145) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp f31e2ccc-e43f-40c9-841b-e3fa7a246a28)) 27 | (pad "2" smd roundrect (at -1.4 -0.250687) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 7dfe50d5-e844-4589-a718-a90f3fd65f32)) 28 | (pad "3" smd roundrect (at -1.4 0.25) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp e36ef46c-2e0b-41e5-b205-3716dd860294)) 29 | (pad "4" smd roundrect (at -1.4 0.741129) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp f1279b8b-ab3a-4ff2-bf7b-94e285bffac6)) 30 | (pad "5" smd roundrect (at 1.397 0.741123) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 347ad7c0-0678-42e1-ac8d-55d5096c9a3d)) 31 | (pad "6" smd roundrect (at 1.397 0.245005) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp a09d4850-1466-425b-a69b-eac82be60404)) 32 | (pad "7" smd roundrect (at 1.397 -0.251113) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 74a9302f-b0ba-475a-b1cc-967145720f57)) 33 | (pad "8" smd roundrect (at 1.397 -0.75) (size 0.6 0.3) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 1053ed68-9207-4a26-98c4-80ecbaf13b90)) 34 | (pad "9" smd roundrect (at 0 0) (size 0.2 1.6) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0) 35 | (chamfer_ratio 0.5) (chamfer top_left) (tstamp 7db1387e-ae84-4760-89f0-813c7ed70421)) 36 | (model "${KICAD6_3DMODEL_DIR}/Package_SON.3dshapes/HVSON-8-1EP_4x4mm_P0.8mm_EP2.2x3.1mm.wrl" 37 | (offset (xyz 0 0 0)) 38 | (scale (xyz 1 1 1)) 39 | (rotate (xyz 0 0 0)) 40 | ) 41 | ) 42 | -------------------------------------------------------------------------------- /pcb/USON-8_4x3mm_P0.8mm.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "USON-8_4x3mm_P0.8mm" (version 20221018) (generator pcbnew) 2 | (layer "F.Cu") 3 | (descr "HVSON, 8 Pin (https://www.nxp.com/docs/en/data-sheet/PCF8523.pdf (page 57)), generated with kicad-footprint-generator ipc_noLead_generator.py") 4 | (tags "HVSON NoLead") 5 | (attr smd) 6 | (fp_text reference "REF**" (at 0 -2.95) (layer "F.SilkS") 7 | (effects (font (size 1 1) (thickness 0.15))) 8 | (tstamp 8a39df6d-76d9-4122-8c81-2696dd7633a4) 9 | ) 10 | (fp_text value "USON-8_4x3mm_P0.8mm" (at 0 2.95) (layer "F.Fab") 11 | (effects (font (size 1 1) (thickness 0.15))) 12 | (tstamp 4aad5a3f-f772-458e-a407-a4617f45f594) 13 | ) 14 | (fp_text user "${REFERENCE}" (at 0 -5.08) (layer "F.Fab") 15 | (effects (font (size 1 1) (thickness 0.15))) 16 | (tstamp 902e59f4-0df8-4efd-bac4-afc8858c9d82) 17 | ) 18 | (fp_line (start -1.524 2.11) (end 1.524 2.11) 19 | (stroke (width 0.12) (type solid)) (layer "F.SilkS") (tstamp d97e0ae0-5623-4aee-8eb9-11a7cb19973e)) 20 | (fp_line (start -1.016 -2.11) (end 1.492 -2.11) 21 | (stroke (width 0.12) (type solid)) (layer "F.SilkS") (tstamp d5f4d9bd-5eff-4f3d-8261-98e14e534465)) 22 | (fp_line (start -2.032 -2.25) (end -2.032 2.25) 23 | (stroke (width 0.05) (type solid)) (layer "F.CrtYd") (tstamp 29674872-ba75-4394-b7c9-c9d57184bdbf)) 24 | (fp_line (start -2.032 2.25) (end 2.032 2.25) 25 | (stroke (width 0.05) (type solid)) (layer "F.CrtYd") (tstamp 45316c2f-562e-4f21-82fb-a9fdb63c3a00)) 26 | (fp_line (start 2.032 -2.25) (end -2.032 -2.25) 27 | (stroke (width 0.05) (type solid)) (layer "F.CrtYd") (tstamp 80b34e9a-9f2e-482b-854b-2d8c7461e69e)) 28 | (fp_line (start 2.032 2.25) (end 2.032 -2.25) 29 | (stroke (width 0.05) (type solid)) (layer "F.CrtYd") (tstamp 6962ed9b-1aaa-458f-8390-9dc73fd1f5f5)) 30 | (fp_line (start -1.524 -1.27) (end -1 -2) 31 | (stroke (width 0.1) (type solid)) (layer "F.Fab") (tstamp a62cff9b-b483-45e9-bbb5-c5005c1d993f)) 32 | (fp_line (start -1.524 2) (end -1.524 -1) 33 | (stroke (width 0.1) (type solid)) (layer "F.Fab") (tstamp 50933a2d-7cf4-48a2-82fb-c034fbf4d38c)) 34 | (fp_line (start -1 -2) (end 1.524 -2) 35 | (stroke (width 0.1) (type solid)) (layer "F.Fab") (tstamp 6b5c4ff2-69b2-4b56-a18d-a267656897c0)) 36 | (fp_line (start 1.524 -2) (end 1.524 2) 37 | (stroke (width 0.1) (type solid)) (layer "F.Fab") (tstamp d8396882-e31e-4aa4-b27e-408c1580191a)) 38 | (fp_line (start 1.524 2) (end -1.524 2) 39 | (stroke (width 0.1) (type solid)) (layer "F.Fab") (tstamp 7947d272-b35c-4195-8df9-c6187b7378e3)) 40 | (pad "1" smd roundrect (at -1.397 -1.2) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp f31e2ccc-e43f-40c9-841b-e3fa7a246a28)) 41 | (pad "2" smd roundrect (at -1.397 -0.4) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 7dfe50d5-e844-4589-a718-a90f3fd65f32)) 42 | (pad "3" smd roundrect (at -1.397 0.4) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp e36ef46c-2e0b-41e5-b205-3716dd860294)) 43 | (pad "4" smd roundrect (at -1.397 1.2) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp f1279b8b-ab3a-4ff2-bf7b-94e285bffac6)) 44 | (pad "5" smd roundrect (at 1.397 1.2) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 347ad7c0-0678-42e1-ac8d-55d5096c9a3d)) 45 | (pad "6" smd roundrect (at 1.397 0.4) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp a09d4850-1466-425b-a69b-eac82be60404)) 46 | (pad "7" smd roundrect (at 1.397 -0.4) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 74a9302f-b0ba-475a-b1cc-967145720f57)) 47 | (pad "8" smd roundrect (at 1.397 -1.2) (size 1 0.35) (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25) (tstamp 1053ed68-9207-4a26-98c4-80ecbaf13b90)) 48 | (pad "9" smd rect (at 0 -0.762) (size 0.258 0.8) (layers "F.Cu" "F.Paste" "F.Mask") (tstamp 7db1387e-ae84-4760-89f0-813c7ed70421)) 49 | (pad "9" smd rect (at 0 0.762) (size 0.258 0.8) (layers "F.Cu" "F.Paste" "F.Mask") (tstamp 7606fc1c-8fad-4bf7-9bec-159d16dce1f4)) 50 | (model "${KICAD6_3DMODEL_DIR}/Package_SON.3dshapes/HVSON-8-1EP_4x4mm_P0.8mm_EP2.2x3.1mm.wrl" 51 | (offset (xyz 0 0 0)) 52 | (scale (xyz 1 1 1)) 53 | (rotate (xyz 0 0 0)) 54 | ) 55 | ) 56 | -------------------------------------------------------------------------------- /pcb/ext.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "ext" (version 20221018) (generator pcbnew) 2 | (layer "F.Cu") 3 | (attr through_hole) 4 | (fp_text reference "REF**" (at -11.6 -1.3) (layer "F.SilkS") 5 | (effects (font (size 1 1) (thickness 0.15))) 6 | (tstamp dd94f8f4-bb3e-46b6-b8ec-830ea48598e5) 7 | ) 8 | (fp_text value "uConsoleExt" (at 3.9 1.3) (layer "F.Fab") 9 | (effects (font (size 1 1) (thickness 0.15))) 10 | (tstamp 355fab69-9151-4dc7-844c-3e624bbe31c6) 11 | ) 12 | (fp_line (start -8.95 -0.7) (end -8.95 -2.5) 13 | (stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp 58852d04-9a9a-4630-a259-5ef4728a8a8a)) 14 | (fp_line (start -1.45 0) (end -8.25 0) 15 | (stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp 50c42d9a-450d-4670-a981-a1aba34267fb)) 16 | (fp_line (start -0.75 -0.7) (end -0.75 -3.25) 17 | (stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp f40f8afc-7907-4003-ab64-d4a13c02e42b)) 18 | (fp_line (start 0.75 -0.7) (end 0.75 -3.25) 19 | (stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp a695ce58-bafb-4ed5-a5c5-feea507e3856)) 20 | (fp_line (start 1.45 0) (end 15.95 0) 21 | (stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp c46f10c7-1029-4c87-8f93-e47d56850210)) 22 | (fp_line (start 16.65 -0.7) (end 16.65 -2.5) 23 | (stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp df9868d0-93d6-4cc5-b2f9-667bcde6a930)) 24 | (fp_arc (start -8.25 0) (mid -8.744975 -0.205025) (end -8.95 -0.7) 25 | (stroke (width 0.05) (type default)) (layer "Edge.Cuts") (tstamp c56fb8df-ac3e-46ed-ad5c-0feaa0dc3e21)) 26 | (fp_arc (start -0.75 -3.25) (mid 0 -4) (end 0.75 -3.25) 27 | (stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp 057ace49-636c-4641-b6c6-818bf805de61)) 28 | (fp_arc (start -0.75 -0.7) (mid -0.955025 -0.205025) (end -1.45 0) 29 | (stroke (width 0.05) (type default)) (layer "Edge.Cuts") (tstamp 0a9aabeb-9bbc-40ff-97b8-cda7ef684d76)) 30 | (fp_arc (start 1.45 0) (mid 0.955025 -0.205025) (end 0.75 -0.7) 31 | (stroke (width 0.05) (type default)) (layer "Edge.Cuts") (tstamp 835abefc-e773-41d1-bb47-8ef5f3281cbb)) 32 | (fp_arc (start 16.65 -0.7) (mid 16.444975 -0.205025) (end 15.95 0) 33 | (stroke (width 0.05) (type default)) (layer "Edge.Cuts") (tstamp 676733b2-dbd7-40f6-a624-abe1373fab62)) 34 | (pad "1" smd rect (at -7.95 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 63cc806f-4490-41d6-8814-f3339d51ad96)) 35 | (pad "2" smd rect (at -7.55 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 717d662b-0360-406c-b8a9-e0066eff0f7d)) 36 | (pad "3" smd rect (at -7.15 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp d034d4c9-9e0c-4bbc-b9bb-fec813b73d25)) 37 | (pad "4" smd rect (at -6.75 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 293c0e08-ecf6-4917-acbf-284b0ee7bcf1)) 38 | (pad "5" smd rect (at -6.35 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 2db86498-db0a-408d-ac6b-42654f1a7872)) 39 | (pad "6" smd rect (at -5.95 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 480a305c-bdc7-40b0-b174-9fa4a1442e4a)) 40 | (pad "7" smd rect (at -5.55 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 233ee924-c20d-44cd-aeae-75365242dbc8)) 41 | (pad "8" smd rect (at -5.15 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 1aaf1cd9-146c-421e-a2f8-a545ba3eae06)) 42 | (pad "9" smd rect (at -4.75 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp b98f69f1-d56a-41d0-bcae-d53361b10043)) 43 | (pad "10" smd rect (at -4.35 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp c7b10db9-cfb1-49dd-ae18-9bd6e27699ac)) 44 | (pad "11" smd rect (at -3.95 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 2f292bec-2e58-4114-b1f3-892d1380c6b4)) 45 | (pad "12" smd rect (at -3.55 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 62c4ef3e-fbc3-4599-a6f1-ab8c501e5e21)) 46 | (pad "13" smd rect (at -3.15 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 7f96e6f9-bba5-4faa-bdb8-9665d4425b74)) 47 | (pad "14" smd rect (at -2.75 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 087825a4-e631-4812-943c-db32e2f1635b)) 48 | (pad "15" smd rect (at -2.35 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 50e7849d-301e-469a-9cf9-ff5227be10ef)) 49 | (pad "16" smd rect (at -1.95 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 9f8926f2-2965-4240-9ca2-26c8f897cd84)) 50 | (pad "17" smd rect (at 1.65 -1.4655) (size 0.6 2.169) (layers "F.Cu" "F.Mask") (tstamp 594bf0ae-f8b1-4d16-b625-5b8136f75655)) 51 | (pad "18" smd rect (at 2.05 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp b6d4780f-6fef-46ea-b3b2-0c98af0ec65c)) 52 | (pad "19" smd rect (at 2.45 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 1518005d-1b49-41ea-b349-510529414453)) 53 | (pad "20" smd rect (at 2.85 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 0b7d6fc6-898f-41cc-80d1-477f588e33de)) 54 | (pad "21" smd rect (at 3.25 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 64cd47b9-89b8-4dee-86df-f61f186fb095)) 55 | (pad "22" smd rect (at 3.65 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 232def9b-f351-4073-b9ce-9b24766e6471)) 56 | (pad "23" smd rect (at 4.05 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 3a9cabb0-60a5-4290-b67c-df136a5e0a3d)) 57 | (pad "24" smd rect (at 4.45 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 4300525a-a10a-4c33-bbea-f6e1532f3383)) 58 | (pad "25" smd rect (at 4.85 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp d45631f4-1b61-4cd3-97cb-e3fd30b31b7c)) 59 | (pad "26" smd rect (at 5.25 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 827dd656-9c51-4fb0-be6e-5ae482f3345b)) 60 | (pad "27" smd rect (at 5.65 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp d8642d24-d0fc-4186-afc9-40810125f78f)) 61 | (pad "28" smd rect (at 6.05 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 80468672-9ed4-4d7f-b044-d07dc6255a94)) 62 | (pad "29" smd rect (at 6.45 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 251229c8-c11d-44f2-a23b-acffbf47b9b4)) 63 | (pad "30" smd rect (at 6.85 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 24477e50-9828-44f3-b8f1-07c3dc453529)) 64 | (pad "31" smd rect (at 7.25 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 51906649-25dd-424d-951e-1f880487ab7c)) 65 | (pad "32" smd rect (at 7.65 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 59e178b0-4b69-42c3-ab7b-9520991685d2)) 66 | (pad "33" smd rect (at 8.05 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 2877ece4-1712-4956-9487-231d6744e6e5)) 67 | (pad "34" smd rect (at 8.45 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 0a42c07d-b1dd-4c42-b748-82297c485979)) 68 | (pad "35" smd rect (at 8.85 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp be1c093e-3dc1-4195-a922-d1c3ab0e889a)) 69 | (pad "36" smd rect (at 9.25 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 7f15722c-168c-48df-8f86-e166efbb84b2)) 70 | (pad "37" smd rect (at 9.65 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp ec2a1877-aca9-4da2-a233-3383c812967e)) 71 | (pad "38" smd rect (at 10.05 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 6de29ef2-eb44-403b-9a83-c991906fc003)) 72 | (pad "39" smd rect (at 10.45 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 726bb2d6-725d-44e0-a4e8-511112b05381)) 73 | (pad "40" smd rect (at 10.85 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp a744fc26-eefc-4a24-9680-d7b43e4464a3)) 74 | (pad "41" smd rect (at 11.25 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 6e69a3ba-4338-4b3e-a452-654bf3610528)) 75 | (pad "42" smd rect (at 11.65 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 151c16bc-15e5-4471-8cea-e70599df41fa)) 76 | (pad "43" smd rect (at 12.05 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp c03d8fd3-5a3e-44ea-97f0-529119c642da)) 77 | (pad "44" smd rect (at 12.45 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 09ad8d56-85fd-4ad2-9ab0-6255c7483d22)) 78 | (pad "45" smd rect (at 12.85 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 38b78fbe-e515-499c-86be-09968032eaec)) 79 | (pad "46" smd rect (at 13.25 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 26b2603f-900a-4254-b176-acbf45f373fa)) 80 | (pad "47" smd rect (at 13.65 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp db3153cc-24b3-47fb-b968-1e58511bdda7)) 81 | (pad "48" smd rect (at 14.05 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 79d74c35-950d-41f9-b65a-20d3cd29492d)) 82 | (pad "49" smd rect (at 14.45 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 756d6dfe-a3fa-4a37-b217-ff7cf8d9ed88)) 83 | (pad "50" smd rect (at 14.85 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 5d717e9d-728c-4bdc-b1ec-5524c9ae1b07)) 84 | (pad "51" smd rect (at 15.25 -1.375) (size 0.6 2.35) (layers "F.Cu" "F.Mask") (tstamp 48f70dc2-243d-4b17-9d8f-b476691d2736)) 85 | (pad "52" smd rect (at 15.65 -1.375) (size 0.6 2.35) (layers "B.Cu" "B.Mask") (tstamp 51d582d7-feaa-4050-b37c-0bb75540160e)) 86 | ) 87 | -------------------------------------------------------------------------------- /pcb/fp-lib-table: -------------------------------------------------------------------------------- 1 | (fp_lib_table 2 | (version 7) 3 | (lib (name "@jb")(type "KiCad")(uri "${KIPRJMOD}/jb-lib/@jb.pretty")(options "")(descr "")) 4 | (lib (name "@upico")(type "KiCad")(uri "${KIPRJMOD}")(options "")(descr "")) 5 | ) 6 | -------------------------------------------------------------------------------- /pcb/speaker.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "speaker" (version 20221018) (generator pcbnew) 2 | (layer "F.Cu") 3 | (property "Sheetname" "") 4 | (property "exclude_from_bom" "") 5 | (attr smd exclude_from_bom) 6 | (fp_text reference "J1" (at 0 6.731 unlocked) (layer "F.SilkS") 7 | (effects (font (size 1 1) (thickness 0.15))) 8 | (tstamp d84295ef-6fa3-4de2-a4bd-145dd3d6c0ed) 9 | ) 10 | (fp_text value "uConsoleSpeaker" (at 0 -5.08 unlocked) (layer "F.Fab") 11 | (effects (font (size 1 1) (thickness 0.15))) 12 | (tstamp 69a45632-4a0d-419d-85c6-0449fb23c063) 13 | ) 14 | (fp_text user "${REFERENCE}" (at 0 -2.54 unlocked) (layer "F.Fab") 15 | (effects (font (size 1 1) (thickness 0.15))) 16 | (tstamp 181a4dae-78d5-4a73-9af8-2edd9a3eb4ec) 17 | ) 18 | (fp_line (start -8.129778 1.93421) (end 8.269986 1.93421) 19 | (stroke (width 0.2) (type solid)) (layer "F.SilkS") (tstamp 3a10f4f0-fa61-4f91-b023-ac244ac90746)) 20 | (fp_line (start 8.269986 10.934446) (end -8.129778 10.934446) 21 | (stroke (width 0.2) (type solid)) (layer "F.SilkS") (tstamp 2b2aa7df-39c8-434e-b207-7a8bf58314c5)) 22 | (pad "1" smd roundrect (at -7.3152 4.7752) (size 3 2.3) (layers "F.Cu" "F.Mask") (roundrect_rratio 0.5) (tstamp 2334a4e7-a392-4b4e-9d79-609947071b4a)) 23 | (pad "2" smd roundrect (at -7.3152 8.0264) (size 3 2.3) (layers "F.Cu" "F.Mask") (roundrect_rratio 0.5) (tstamp 945de038-84e9-4523-927b-3320ed93dabf)) 24 | (pad "3" smd roundrect (at 7.410156 8.0264) (size 3 2.3) (layers "F.Cu" "F.Mask") (roundrect_rratio 0.5) (tstamp baf4c561-ec20-44da-a696-513687cdc570)) 25 | (pad "4" smd roundrect (at 7.410156 4.7752) (size 3 2.3) (layers "F.Cu" "F.Mask") (roundrect_rratio 0.5) (tstamp 3de3d300-79ec-4399-b25f-f2cf3cc68512)) 26 | ) 27 | -------------------------------------------------------------------------------- /pcb/sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | (version 7) 3 | (lib (name "@jb")(type "KiCad")(uri "${KIPRJMOD}/jb-lib/@jb.kicad_sym")(options "")(descr "")) 4 | (lib (name "@upico")(type "KiCad")(uri "${KIPRJMOD}/upico.kicad_sym")(options "")(descr "")) 5 | ) 6 | -------------------------------------------------------------------------------- /pcb/upico.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 37, 4 | "active_layer_preset": "", 5 | "auto_track_width": true, 6 | "hidden_netclasses": [], 7 | "hidden_nets": [], 8 | "high_contrast_mode": 0, 9 | "net_color_mode": 1, 10 | "opacity": { 11 | "images": 0.6, 12 | "pads": 1.0, 13 | "tracks": 1.0, 14 | "vias": 1.0, 15 | "zones": 0.6 16 | }, 17 | "selection_filter": { 18 | "dimensions": true, 19 | "footprints": true, 20 | "graphics": true, 21 | "keepouts": true, 22 | "lockedItems": true, 23 | "otherItems": true, 24 | "pads": true, 25 | "text": true, 26 | "tracks": true, 27 | "vias": true, 28 | "zones": true 29 | }, 30 | "visible_items": [ 31 | 0, 32 | 1, 33 | 2, 34 | 3, 35 | 4, 36 | 5, 37 | 8, 38 | 9, 39 | 10, 40 | 11, 41 | 12, 42 | 13, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36, 64 | 37, 65 | 39, 66 | 40 67 | ], 68 | "visible_layers": "ffcffff_ffffffff", 69 | "zone_display_mode": 1 70 | }, 71 | "git": { 72 | "repo_password": "", 73 | "repo_type": "", 74 | "repo_username": "", 75 | "ssh_key": "" 76 | }, 77 | "meta": { 78 | "filename": "upico.kicad_prl", 79 | "version": 3 80 | }, 81 | "project": { 82 | "files": [] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /pcb/upico.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "3dviewports": [], 4 | "design_settings": { 5 | "defaults": { 6 | "board_outline_line_width": 0.049999999999999996, 7 | "copper_line_width": 0.19999999999999998, 8 | "copper_text_italic": false, 9 | "copper_text_size_h": 1.5, 10 | "copper_text_size_v": 1.5, 11 | "copper_text_thickness": 0.3, 12 | "copper_text_upright": false, 13 | "courtyard_line_width": 0.049999999999999996, 14 | "dimension_precision": 4, 15 | "dimension_units": 3, 16 | "dimensions": { 17 | "arrow_length": 1270000, 18 | "extension_offset": 500000, 19 | "keep_text_aligned": true, 20 | "suppress_zeroes": false, 21 | "text_position": 0, 22 | "units_format": 1 23 | }, 24 | "fab_line_width": 0.09999999999999999, 25 | "fab_text_italic": false, 26 | "fab_text_size_h": 1.0, 27 | "fab_text_size_v": 1.0, 28 | "fab_text_thickness": 0.15, 29 | "fab_text_upright": false, 30 | "other_line_width": 0.09999999999999999, 31 | "other_text_italic": false, 32 | "other_text_size_h": 1.0, 33 | "other_text_size_v": 1.0, 34 | "other_text_thickness": 0.15, 35 | "other_text_upright": false, 36 | "pads": { 37 | "drill": 0.762, 38 | "height": 1.524, 39 | "width": 1.524 40 | }, 41 | "silk_line_width": 0.15, 42 | "silk_text_italic": false, 43 | "silk_text_size_h": 0.7999999999999999, 44 | "silk_text_size_v": 0.7999999999999999, 45 | "silk_text_thickness": 0.15, 46 | "silk_text_upright": false, 47 | "zones": { 48 | "min_clearance": 0.128 49 | } 50 | }, 51 | "diff_pair_dimensions": [ 52 | { 53 | "gap": 0.0, 54 | "via_gap": 0.0, 55 | "width": 0.0 56 | } 57 | ], 58 | "drc_exclusions": [], 59 | "meta": { 60 | "version": 2 61 | }, 62 | "rule_severities": { 63 | "annular_width": "error", 64 | "clearance": "error", 65 | "connection_width": "warning", 66 | "copper_edge_clearance": "error", 67 | "copper_sliver": "warning", 68 | "courtyards_overlap": "error", 69 | "diff_pair_gap_out_of_range": "error", 70 | "diff_pair_uncoupled_length_too_long": "error", 71 | "drill_out_of_range": "error", 72 | "duplicate_footprints": "warning", 73 | "extra_footprint": "warning", 74 | "footprint": "error", 75 | "footprint_type_mismatch": "ignore", 76 | "hole_clearance": "error", 77 | "hole_near_hole": "error", 78 | "invalid_outline": "error", 79 | "isolated_copper": "warning", 80 | "item_on_disabled_layer": "error", 81 | "items_not_allowed": "error", 82 | "length_out_of_range": "error", 83 | "lib_footprint_issues": "warning", 84 | "lib_footprint_mismatch": "warning", 85 | "malformed_courtyard": "error", 86 | "microvia_drill_out_of_range": "error", 87 | "missing_courtyard": "ignore", 88 | "missing_footprint": "warning", 89 | "net_conflict": "warning", 90 | "npth_inside_courtyard": "ignore", 91 | "padstack": "warning", 92 | "pth_inside_courtyard": "ignore", 93 | "shorting_items": "error", 94 | "silk_edge_clearance": "ignore", 95 | "silk_over_copper": "warning", 96 | "silk_overlap": "warning", 97 | "skew_out_of_range": "error", 98 | "solder_mask_bridge": "error", 99 | "starved_thermal": "error", 100 | "text_height": "warning", 101 | "text_thickness": "warning", 102 | "through_hole_pad_without_hole": "error", 103 | "too_many_vias": "error", 104 | "track_dangling": "warning", 105 | "track_width": "error", 106 | "tracks_crossing": "error", 107 | "unconnected_items": "error", 108 | "unresolved_variable": "error", 109 | "via_dangling": "warning", 110 | "zones_intersect": "error" 111 | }, 112 | "rules": { 113 | "max_error": 0.005, 114 | "min_clearance": 0.13999999999999999, 115 | "min_connection": 0.0, 116 | "min_copper_edge_clearance": 0.19999999999999998, 117 | "min_hole_clearance": 0.25, 118 | "min_hole_to_hole": 0.25, 119 | "min_microvia_diameter": 0.39999999999999997, 120 | "min_microvia_drill": 0.19999999999999998, 121 | "min_resolved_spokes": 2, 122 | "min_silk_clearance": 0.0, 123 | "min_text_height": 0.7, 124 | "min_text_thickness": 0.12, 125 | "min_through_hole_diameter": 0.19999999999999998, 126 | "min_track_width": 0.13999999999999999, 127 | "min_via_annular_width": 0.13999999999999999, 128 | "min_via_diameter": 0.44999999999999996, 129 | "solder_mask_to_copper_clearance": 0.0, 130 | "use_height_for_length_calcs": true 131 | }, 132 | "teardrop_options": [ 133 | { 134 | "td_allow_use_two_tracks": true, 135 | "td_curve_segcount": 5, 136 | "td_on_pad_in_zone": true, 137 | "td_onpadsmd": true, 138 | "td_onroundshapesonly": false, 139 | "td_ontrackend": true, 140 | "td_onviapad": true 141 | } 142 | ], 143 | "teardrop_parameters": [ 144 | { 145 | "td_curve_segcount": 0, 146 | "td_height_ratio": 1.0, 147 | "td_length_ratio": 0.5, 148 | "td_maxheight": 2.0, 149 | "td_maxlen": 1.0, 150 | "td_target_name": "td_round_shape", 151 | "td_width_to_size_filter_ratio": 0.9 152 | }, 153 | { 154 | "td_curve_segcount": 0, 155 | "td_height_ratio": 1.0, 156 | "td_length_ratio": 0.5, 157 | "td_maxheight": 2.0, 158 | "td_maxlen": 1.0, 159 | "td_target_name": "td_rect_shape", 160 | "td_width_to_size_filter_ratio": 0.9 161 | }, 162 | { 163 | "td_curve_segcount": 0, 164 | "td_height_ratio": 1.0, 165 | "td_length_ratio": 0.5, 166 | "td_maxheight": 2.0, 167 | "td_maxlen": 1.0, 168 | "td_target_name": "td_track_end", 169 | "td_width_to_size_filter_ratio": 0.9 170 | } 171 | ], 172 | "track_widths": [ 173 | 0.0, 174 | 0.127, 175 | 0.2, 176 | 0.3 177 | ], 178 | "via_dimensions": [ 179 | { 180 | "diameter": 0.0, 181 | "drill": 0.0 182 | }, 183 | { 184 | "diameter": 0.5, 185 | "drill": 0.2 186 | }, 187 | { 188 | "diameter": 0.7, 189 | "drill": 0.3 190 | } 191 | ], 192 | "zones_allow_external_fillets": false 193 | }, 194 | "ipc2581": { 195 | "dist": "", 196 | "distpn": "", 197 | "internal_id": "", 198 | "mfg": "", 199 | "mpn": "" 200 | }, 201 | "layer_presets": [], 202 | "viewports": [] 203 | }, 204 | "boards": [], 205 | "cvpcb": { 206 | "equivalence_files": [] 207 | }, 208 | "erc": { 209 | "erc_exclusions": [], 210 | "meta": { 211 | "version": 0 212 | }, 213 | "pin_map": [ 214 | [ 215 | 0, 216 | 0, 217 | 0, 218 | 0, 219 | 0, 220 | 0, 221 | 1, 222 | 0, 223 | 0, 224 | 0, 225 | 0, 226 | 2 227 | ], 228 | [ 229 | 0, 230 | 2, 231 | 0, 232 | 1, 233 | 0, 234 | 0, 235 | 1, 236 | 0, 237 | 2, 238 | 2, 239 | 2, 240 | 2 241 | ], 242 | [ 243 | 0, 244 | 0, 245 | 0, 246 | 0, 247 | 0, 248 | 0, 249 | 1, 250 | 0, 251 | 1, 252 | 0, 253 | 1, 254 | 2 255 | ], 256 | [ 257 | 0, 258 | 1, 259 | 0, 260 | 0, 261 | 0, 262 | 0, 263 | 1, 264 | 1, 265 | 2, 266 | 1, 267 | 1, 268 | 2 269 | ], 270 | [ 271 | 0, 272 | 0, 273 | 0, 274 | 0, 275 | 0, 276 | 0, 277 | 1, 278 | 0, 279 | 0, 280 | 0, 281 | 0, 282 | 2 283 | ], 284 | [ 285 | 0, 286 | 0, 287 | 0, 288 | 0, 289 | 0, 290 | 0, 291 | 0, 292 | 0, 293 | 0, 294 | 0, 295 | 0, 296 | 2 297 | ], 298 | [ 299 | 1, 300 | 1, 301 | 1, 302 | 1, 303 | 1, 304 | 0, 305 | 1, 306 | 1, 307 | 1, 308 | 1, 309 | 1, 310 | 2 311 | ], 312 | [ 313 | 0, 314 | 0, 315 | 0, 316 | 1, 317 | 0, 318 | 0, 319 | 1, 320 | 0, 321 | 0, 322 | 0, 323 | 0, 324 | 2 325 | ], 326 | [ 327 | 0, 328 | 2, 329 | 1, 330 | 2, 331 | 0, 332 | 0, 333 | 1, 334 | 0, 335 | 2, 336 | 2, 337 | 2, 338 | 2 339 | ], 340 | [ 341 | 0, 342 | 2, 343 | 0, 344 | 1, 345 | 0, 346 | 0, 347 | 1, 348 | 0, 349 | 2, 350 | 0, 351 | 0, 352 | 2 353 | ], 354 | [ 355 | 0, 356 | 2, 357 | 1, 358 | 1, 359 | 0, 360 | 0, 361 | 1, 362 | 0, 363 | 2, 364 | 0, 365 | 0, 366 | 2 367 | ], 368 | [ 369 | 2, 370 | 2, 371 | 2, 372 | 2, 373 | 2, 374 | 2, 375 | 2, 376 | 2, 377 | 2, 378 | 2, 379 | 2, 380 | 2 381 | ] 382 | ], 383 | "rule_severities": { 384 | "bus_definition_conflict": "error", 385 | "bus_entry_needed": "error", 386 | "bus_to_bus_conflict": "error", 387 | "bus_to_net_conflict": "error", 388 | "conflicting_netclasses": "error", 389 | "different_unit_footprint": "error", 390 | "different_unit_net": "error", 391 | "duplicate_reference": "error", 392 | "duplicate_sheet_names": "error", 393 | "endpoint_off_grid": "warning", 394 | "extra_units": "error", 395 | "global_label_dangling": "warning", 396 | "hier_label_mismatch": "error", 397 | "label_dangling": "error", 398 | "lib_symbol_issues": "warning", 399 | "missing_bidi_pin": "warning", 400 | "missing_input_pin": "warning", 401 | "missing_power_pin": "error", 402 | "missing_unit": "warning", 403 | "multiple_net_names": "warning", 404 | "net_not_bus_member": "warning", 405 | "no_connect_connected": "warning", 406 | "no_connect_dangling": "warning", 407 | "pin_not_connected": "error", 408 | "pin_not_driven": "error", 409 | "pin_to_pin": "warning", 410 | "power_pin_not_driven": "error", 411 | "similar_labels": "warning", 412 | "simulation_model_issue": "ignore", 413 | "unannotated": "error", 414 | "unit_value_mismatch": "error", 415 | "unresolved_variable": "error", 416 | "wire_dangling": "error" 417 | } 418 | }, 419 | "libraries": { 420 | "pinned_footprint_libs": [], 421 | "pinned_symbol_libs": [] 422 | }, 423 | "meta": { 424 | "filename": "upico.kicad_pro", 425 | "version": 1 426 | }, 427 | "net_settings": { 428 | "classes": [ 429 | { 430 | "bus_width": 12, 431 | "clearance": 0.15, 432 | "diff_pair_gap": 0.25, 433 | "diff_pair_via_gap": 0.25, 434 | "diff_pair_width": 0.2, 435 | "line_style": 0, 436 | "microvia_diameter": 0.2, 437 | "microvia_drill": 0.1, 438 | "name": "Default", 439 | "pcb_color": "rgba(0, 0, 0, 0.000)", 440 | "schematic_color": "rgba(0, 0, 0, 0.000)", 441 | "track_width": 0.127, 442 | "via_diameter": 0.5, 443 | "via_drill": 0.2, 444 | "wire_width": 6 445 | }, 446 | { 447 | "bus_width": 12, 448 | "clearance": 0.15, 449 | "diff_pair_gap": 0.3, 450 | "diff_pair_via_gap": 0.25, 451 | "diff_pair_width": 0.1598, 452 | "line_style": 0, 453 | "microvia_diameter": 0.2, 454 | "microvia_drill": 0.1, 455 | "name": "MIPI", 456 | "pcb_color": "rgba(0, 0, 0, 0.000)", 457 | "schematic_color": "rgba(0, 0, 0, 0.000)", 458 | "track_width": 0.1374, 459 | "via_diameter": 0.5, 460 | "via_drill": 0.2, 461 | "wire_width": 6 462 | }, 463 | { 464 | "bus_width": 12, 465 | "clearance": 0.15, 466 | "diff_pair_gap": 0.25, 467 | "diff_pair_via_gap": 0.25, 468 | "diff_pair_width": 0.2, 469 | "line_style": 0, 470 | "microvia_diameter": 0.2, 471 | "microvia_drill": 0.1, 472 | "name": "Power", 473 | "pcb_color": "rgba(0, 0, 0, 0.000)", 474 | "schematic_color": "rgba(0, 0, 0, 0.000)", 475 | "track_width": 0.2, 476 | "via_diameter": 0.5, 477 | "via_drill": 0.2, 478 | "wire_width": 6 479 | }, 480 | { 481 | "bus_width": 12, 482 | "clearance": 0.15, 483 | "diff_pair_gap": 0.15, 484 | "diff_pair_via_gap": 0.25, 485 | "diff_pair_width": 0.45, 486 | "line_style": 0, 487 | "microvia_diameter": 0.3, 488 | "microvia_drill": 0.1, 489 | "name": "USB", 490 | "pcb_color": "rgba(0, 0, 0, 0.000)", 491 | "schematic_color": "rgba(0, 0, 0, 0.000)", 492 | "track_width": 0.45, 493 | "via_diameter": 0.5, 494 | "via_drill": 0.2, 495 | "wire_width": 6 496 | } 497 | ], 498 | "meta": { 499 | "version": 3 500 | }, 501 | "net_colors": null, 502 | "netclass_assignments": null, 503 | "netclass_patterns": [ 504 | { 505 | "netclass": "USB", 506 | "pattern": "*USB_D*" 507 | }, 508 | { 509 | "netclass": "MIPI", 510 | "pattern": "*CAM_D*" 511 | }, 512 | { 513 | "netclass": "MIPI", 514 | "pattern": "*CAM_C*" 515 | }, 516 | { 517 | "netclass": "Power", 518 | "pattern": "GND" 519 | }, 520 | { 521 | "netclass": "Power", 522 | "pattern": "VDD" 523 | }, 524 | { 525 | "netclass": "Power", 526 | "pattern": "+*" 527 | }, 528 | { 529 | "netclass": "Power", 530 | "pattern": "VBUS" 531 | } 532 | ] 533 | }, 534 | "pcbnew": { 535 | "last_paths": { 536 | "gencad": "", 537 | "idf": "", 538 | "netlist": "", 539 | "plot": "", 540 | "pos_files": "", 541 | "specctra_dsn": "", 542 | "step": "", 543 | "svg": "", 544 | "vrml": "" 545 | }, 546 | "page_layout_descr_file": "" 547 | }, 548 | "schematic": { 549 | "annotate_start_num": 0, 550 | "bom_fmt_presets": [], 551 | "bom_fmt_settings": { 552 | "field_delimiter": ",", 553 | "keep_line_breaks": false, 554 | "keep_tabs": false, 555 | "name": "CSV", 556 | "ref_delimiter": ",", 557 | "ref_range_delimiter": "", 558 | "string_delimiter": "\"" 559 | }, 560 | "bom_presets": [], 561 | "bom_settings": { 562 | "exclude_dnp": false, 563 | "fields_ordered": [ 564 | { 565 | "group_by": false, 566 | "label": "Reference", 567 | "name": "Reference", 568 | "show": true 569 | }, 570 | { 571 | "group_by": true, 572 | "label": "Value", 573 | "name": "Value", 574 | "show": true 575 | }, 576 | { 577 | "group_by": false, 578 | "label": "Datasheet", 579 | "name": "Datasheet", 580 | "show": true 581 | }, 582 | { 583 | "group_by": false, 584 | "label": "Footprint", 585 | "name": "Footprint", 586 | "show": true 587 | }, 588 | { 589 | "group_by": false, 590 | "label": "Qty", 591 | "name": "${QUANTITY}", 592 | "show": true 593 | }, 594 | { 595 | "group_by": true, 596 | "label": "DNP", 597 | "name": "${DNP}", 598 | "show": true 599 | } 600 | ], 601 | "filter_string": "", 602 | "group_symbols": true, 603 | "name": "Grouped By Value", 604 | "sort_asc": true, 605 | "sort_field": "Reference" 606 | }, 607 | "connection_grid_size": 50.0, 608 | "drawing": { 609 | "dashed_lines_dash_length_ratio": 12.0, 610 | "dashed_lines_gap_length_ratio": 3.0, 611 | "default_line_thickness": 6.0, 612 | "default_text_size": 50.0, 613 | "field_names": [], 614 | "intersheets_ref_own_page": false, 615 | "intersheets_ref_prefix": "", 616 | "intersheets_ref_short": false, 617 | "intersheets_ref_show": false, 618 | "intersheets_ref_suffix": "", 619 | "junction_size_choice": 3, 620 | "label_size_ratio": 0.25, 621 | "operating_point_overlay_i_precision": 3, 622 | "operating_point_overlay_i_range": "~A", 623 | "operating_point_overlay_v_precision": 3, 624 | "operating_point_overlay_v_range": "~V", 625 | "overbar_offset_ratio": 1.23, 626 | "pin_symbol_size": 0.0, 627 | "text_offset_ratio": 0.08 628 | }, 629 | "legacy_lib_dir": "", 630 | "legacy_lib_list": [], 631 | "meta": { 632 | "version": 1 633 | }, 634 | "net_format_name": "", 635 | "page_layout_descr_file": "", 636 | "plot_directory": "../docs/", 637 | "spice_current_sheet_as_root": false, 638 | "spice_external_command": "spice \"%I\"", 639 | "spice_model_current_sheet_as_root": true, 640 | "spice_save_all_currents": false, 641 | "spice_save_all_dissipations": false, 642 | "spice_save_all_voltages": false, 643 | "subpart_first_id": 65, 644 | "subpart_id_separator": 0 645 | }, 646 | "sheets": [ 647 | [ 648 | "983c426c-24e0-4c65-ab69-1f1824adc5c6", 649 | "Root" 650 | ] 651 | ], 652 | "text_variables": {} 653 | } 654 | -------------------------------------------------------------------------------- /pcb/upico.kicad_sym: -------------------------------------------------------------------------------- 1 | (kicad_symbol_lib (version 20220914) (generator kicad_symbol_editor) 2 | (symbol "uConsoleExt" (pin_names (offset 1.016)) (in_bom yes) (on_board yes) 3 | (property "Reference" "J" (at 1.27 33.02 0) 4 | (effects (font (size 1.27 1.27))) 5 | ) 6 | (property "Value" "uConsoleEXT" (at 1.27 -41.91 0) 7 | (effects (font (size 1.27 1.27))) 8 | ) 9 | (property "Footprint" "@upico:ext" (at 1.27 -44.45 0) 10 | (effects (font (size 1.27 1.27)) hide) 11 | ) 12 | (property "Datasheet" "~" (at 0 0 0) 13 | (effects (font (size 1.27 1.27)) hide) 14 | ) 15 | (property "ki_keywords" "mini PCI-E" (at 0 0 0) 16 | (effects (font (size 1.27 1.27)) hide) 17 | ) 18 | (property "ki_fp_filters" "Connector*:*_2x??_*" (at 0 0 0) 19 | (effects (font (size 1.27 1.27)) hide) 20 | ) 21 | (symbol "uConsoleExt_0_1" 22 | (arc (start -11.3793 7.3977) (mid -10.5902 9.3027) (end -11.3793 11.2077) 23 | (stroke (width 0) (type default)) 24 | (fill (type none)) 25 | ) 26 | (arc (start 13.9259 10.8153) (mid 13.1368 8.9103) (end 13.9259 7.0053) 27 | (stroke (width 0) (type default)) 28 | (fill (type none)) 29 | ) 30 | ) 31 | (symbol "uConsoleExt_1_1" 32 | (rectangle (start -11.43 31.75) (end 13.97 -39.37) 33 | (stroke (width 0.254) (type default)) 34 | (fill (type background)) 35 | ) 36 | (pin passive line (at -15.24 30.48 0) (length 3.81) 37 | (name "SPK_LP" (effects (font (size 1.27 1.27)))) 38 | (number "1" (effects (font (size 1.27 1.27)))) 39 | ) 40 | (pin power_in line (at 17.78 20.32 180) (length 3.81) 41 | (name "5V" (effects (font (size 1.27 1.27)))) 42 | (number "10" (effects (font (size 1.27 1.27)))) 43 | ) 44 | (pin passive line (at -15.24 17.78 0) (length 3.81) 45 | (name "GND" (effects (font (size 1.27 1.27)))) 46 | (number "11" (effects (font (size 1.27 1.27)))) 47 | ) 48 | (pin power_in line (at 17.78 17.78 180) (length 3.81) 49 | (name "GND" (effects (font (size 1.27 1.27)))) 50 | (number "12" (effects (font (size 1.27 1.27)))) 51 | ) 52 | (pin passive line (at -15.24 15.24 0) (length 3.81) 53 | (name "USB4_DP" (effects (font (size 1.27 1.27)))) 54 | (number "13" (effects (font (size 1.27 1.27)))) 55 | ) 56 | (pin passive line (at 17.78 15.24 180) (length 3.81) 57 | (name "SPK_RP" (effects (font (size 1.27 1.27)))) 58 | (number "14" (effects (font (size 1.27 1.27)))) 59 | ) 60 | (pin passive line (at -15.24 12.7 0) (length 3.81) 61 | (name "USB4_DM" (effects (font (size 1.27 1.27)))) 62 | (number "15" (effects (font (size 1.27 1.27)))) 63 | ) 64 | (pin passive line (at 17.78 12.7 180) (length 3.81) 65 | (name "SPK_RN" (effects (font (size 1.27 1.27)))) 66 | (number "16" (effects (font (size 1.27 1.27)))) 67 | ) 68 | (pin power_in line (at -15.24 5.08 0) (length 3.81) 69 | (name "3V3" (effects (font (size 1.27 1.27)))) 70 | (number "17" (effects (font (size 1.27 1.27)))) 71 | ) 72 | (pin passive line (at 17.78 5.08 180) (length 3.81) 73 | (name "GPIO28" (effects (font (size 1.27 1.27)))) 74 | (number "18" (effects (font (size 1.27 1.27)))) 75 | ) 76 | (pin power_in line (at -15.24 2.54 0) (length 3.81) 77 | (name "3V3" (effects (font (size 1.27 1.27)))) 78 | (number "19" (effects (font (size 1.27 1.27)))) 79 | ) 80 | (pin power_in line (at 17.78 30.48 180) (length 3.81) 81 | (name "5V" (effects (font (size 1.27 1.27)))) 82 | (number "2" (effects (font (size 1.27 1.27)))) 83 | ) 84 | (pin passive line (at 17.78 2.54 180) (length 3.81) 85 | (name "GPIO29" (effects (font (size 1.27 1.27)))) 86 | (number "20" (effects (font (size 1.27 1.27)))) 87 | ) 88 | (pin passive line (at -15.24 0 0) (length 3.81) 89 | (name "GND" (effects (font (size 1.27 1.27)))) 90 | (number "21" (effects (font (size 1.27 1.27)))) 91 | ) 92 | (pin passive line (at 17.78 0 180) (length 3.81) 93 | (name "GPIO30" (effects (font (size 1.27 1.27)))) 94 | (number "22" (effects (font (size 1.27 1.27)))) 95 | ) 96 | (pin passive line (at -15.24 -2.54 0) (length 3.81) 97 | (name "CAM_DP3" (effects (font (size 1.27 1.27)))) 98 | (number "23" (effects (font (size 1.27 1.27)))) 99 | ) 100 | (pin passive line (at 17.78 -2.54 180) (length 3.81) 101 | (name "GPIO31" (effects (font (size 1.27 1.27)))) 102 | (number "24" (effects (font (size 1.27 1.27)))) 103 | ) 104 | (pin passive line (at -15.24 -5.08 0) (length 3.81) 105 | (name "CAM_DN3" (effects (font (size 1.27 1.27)))) 106 | (number "25" (effects (font (size 1.27 1.27)))) 107 | ) 108 | (pin passive line (at 17.78 -5.08 180) (length 3.81) 109 | (name "GPIO32" (effects (font (size 1.27 1.27)))) 110 | (number "26" (effects (font (size 1.27 1.27)))) 111 | ) 112 | (pin passive line (at -15.24 -7.62 0) (length 3.81) 113 | (name "GND" (effects (font (size 1.27 1.27)))) 114 | (number "27" (effects (font (size 1.27 1.27)))) 115 | ) 116 | (pin passive line (at 17.78 -7.62 180) (length 3.81) 117 | (name "GPIO33" (effects (font (size 1.27 1.27)))) 118 | (number "28" (effects (font (size 1.27 1.27)))) 119 | ) 120 | (pin passive line (at -15.24 -10.16 0) (length 3.81) 121 | (name "CAM_DP2" (effects (font (size 1.27 1.27)))) 122 | (number "29" (effects (font (size 1.27 1.27)))) 123 | ) 124 | (pin passive line (at -15.24 27.94 0) (length 3.81) 125 | (name "SPK_LN" (effects (font (size 1.27 1.27)))) 126 | (number "3" (effects (font (size 1.27 1.27)))) 127 | ) 128 | (pin passive line (at 17.78 -10.16 180) (length 3.81) 129 | (name "GPIO34" (effects (font (size 1.27 1.27)))) 130 | (number "30" (effects (font (size 1.27 1.27)))) 131 | ) 132 | (pin passive line (at -15.24 -12.7 0) (length 3.81) 133 | (name "CAM_DN2" (effects (font (size 1.27 1.27)))) 134 | (number "31" (effects (font (size 1.27 1.27)))) 135 | ) 136 | (pin passive line (at 17.78 -12.7 180) (length 3.81) 137 | (name "GPIO35" (effects (font (size 1.27 1.27)))) 138 | (number "32" (effects (font (size 1.27 1.27)))) 139 | ) 140 | (pin passive line (at -15.24 -15.24 0) (length 3.81) 141 | (name "GND" (effects (font (size 1.27 1.27)))) 142 | (number "33" (effects (font (size 1.27 1.27)))) 143 | ) 144 | (pin passive line (at 17.78 -15.24 180) (length 3.81) 145 | (name "GPIO36" (effects (font (size 1.27 1.27)))) 146 | (number "34" (effects (font (size 1.27 1.27)))) 147 | ) 148 | (pin passive line (at -15.24 -17.78 0) (length 3.81) 149 | (name "CAM_CP" (effects (font (size 1.27 1.27)))) 150 | (number "35" (effects (font (size 1.27 1.27)))) 151 | ) 152 | (pin passive line (at 17.78 -17.78 180) (length 3.81) 153 | (name "GPIO37" (effects (font (size 1.27 1.27)))) 154 | (number "36" (effects (font (size 1.27 1.27)))) 155 | ) 156 | (pin passive line (at -15.24 -20.32 0) (length 3.81) 157 | (name "CAM_CN" (effects (font (size 1.27 1.27)))) 158 | (number "37" (effects (font (size 1.27 1.27)))) 159 | ) 160 | (pin passive line (at 17.78 -20.32 180) (length 3.81) 161 | (name "GPIO38" (effects (font (size 1.27 1.27)))) 162 | (number "38" (effects (font (size 1.27 1.27)))) 163 | ) 164 | (pin passive line (at -15.24 -22.86 0) (length 3.81) 165 | (name "GND" (effects (font (size 1.27 1.27)))) 166 | (number "39" (effects (font (size 1.27 1.27)))) 167 | ) 168 | (pin power_in line (at 17.78 27.94 180) (length 3.81) 169 | (name "5V" (effects (font (size 1.27 1.27)))) 170 | (number "4" (effects (font (size 1.27 1.27)))) 171 | ) 172 | (pin passive line (at 17.78 -22.86 180) (length 3.81) 173 | (name "GPIO39" (effects (font (size 1.27 1.27)))) 174 | (number "40" (effects (font (size 1.27 1.27)))) 175 | ) 176 | (pin passive line (at -15.24 -25.4 0) (length 3.81) 177 | (name "CAM_DP1" (effects (font (size 1.27 1.27)))) 178 | (number "41" (effects (font (size 1.27 1.27)))) 179 | ) 180 | (pin passive line (at 17.78 -25.4 180) (length 3.81) 181 | (name "GPIO40" (effects (font (size 1.27 1.27)))) 182 | (number "42" (effects (font (size 1.27 1.27)))) 183 | ) 184 | (pin passive line (at -15.24 -27.94 0) (length 3.81) 185 | (name "CAM_DN1" (effects (font (size 1.27 1.27)))) 186 | (number "43" (effects (font (size 1.27 1.27)))) 187 | ) 188 | (pin passive line (at 17.78 -27.94 180) (length 3.81) 189 | (name "GPIO41" (effects (font (size 1.27 1.27)))) 190 | (number "44" (effects (font (size 1.27 1.27)))) 191 | ) 192 | (pin passive line (at -15.24 -30.48 0) (length 3.81) 193 | (name "GND" (effects (font (size 1.27 1.27)))) 194 | (number "45" (effects (font (size 1.27 1.27)))) 195 | ) 196 | (pin passive line (at 17.78 -30.48 180) (length 3.81) 197 | (name "GPIO42" (effects (font (size 1.27 1.27)))) 198 | (number "46" (effects (font (size 1.27 1.27)))) 199 | ) 200 | (pin passive line (at -15.24 -33.02 0) (length 3.81) 201 | (name "CAM_DP0" (effects (font (size 1.27 1.27)))) 202 | (number "47" (effects (font (size 1.27 1.27)))) 203 | ) 204 | (pin passive line (at 17.78 -33.02 180) (length 3.81) 205 | (name "GPIO43" (effects (font (size 1.27 1.27)))) 206 | (number "48" (effects (font (size 1.27 1.27)))) 207 | ) 208 | (pin passive line (at -15.24 -35.56 0) (length 3.81) 209 | (name "CAM_DN0" (effects (font (size 1.27 1.27)))) 210 | (number "49" (effects (font (size 1.27 1.27)))) 211 | ) 212 | (pin passive line (at -15.24 25.4 0) (length 3.81) 213 | (name "GND" (effects (font (size 1.27 1.27)))) 214 | (number "5" (effects (font (size 1.27 1.27)))) 215 | ) 216 | (pin passive line (at 17.78 -35.56 180) (length 3.81) 217 | (name "GPIO44" (effects (font (size 1.27 1.27)))) 218 | (number "50" (effects (font (size 1.27 1.27)))) 219 | ) 220 | (pin passive line (at -15.24 -38.1 0) (length 3.81) 221 | (name "GND" (effects (font (size 1.27 1.27)))) 222 | (number "51" (effects (font (size 1.27 1.27)))) 223 | ) 224 | (pin passive line (at 17.78 -38.1 180) (length 3.81) 225 | (name "GPIO45" (effects (font (size 1.27 1.27)))) 226 | (number "52" (effects (font (size 1.27 1.27)))) 227 | ) 228 | (pin power_in line (at 17.78 25.4 180) (length 3.81) 229 | (name "5V" (effects (font (size 1.27 1.27)))) 230 | (number "6" (effects (font (size 1.27 1.27)))) 231 | ) 232 | (pin passive line (at -15.24 22.86 0) (length 3.81) 233 | (name "USB3_DP" (effects (font (size 1.27 1.27)))) 234 | (number "7" (effects (font (size 1.27 1.27)))) 235 | ) 236 | (pin power_in line (at 17.78 22.86 180) (length 3.81) 237 | (name "5V" (effects (font (size 1.27 1.27)))) 238 | (number "8" (effects (font (size 1.27 1.27)))) 239 | ) 240 | (pin passive line (at -15.24 20.32 0) (length 3.81) 241 | (name "USB3_DM" (effects (font (size 1.27 1.27)))) 242 | (number "9" (effects (font (size 1.27 1.27)))) 243 | ) 244 | ) 245 | ) 246 | (symbol "uConsoleSpeaker" (in_bom no) (on_board yes) 247 | (property "Reference" "J" (at 0 -16.51 0) 248 | (effects (font (size 1.27 1.27))) 249 | ) 250 | (property "Value" "uConsoleSpeaker" (at 0 1.27 0) 251 | (effects (font (size 1.27 1.27))) 252 | ) 253 | (property "Footprint" "@upico:speaker" (at 0 0 0) 254 | (effects (font (size 1.27 1.27)) hide) 255 | ) 256 | (property "Datasheet" "" (at 0 0 0) 257 | (effects (font (size 1.27 1.27)) hide) 258 | ) 259 | (symbol "uConsoleSpeaker_0_1" 260 | (rectangle (start -10.16 -1.27) (end 10.16 -13.97) 261 | (stroke (width 0) (type default)) 262 | (fill (type none)) 263 | ) 264 | (circle (center 0 -7.62) (radius 3.5921) 265 | (stroke (width 0) (type default)) 266 | (fill (type none)) 267 | ) 268 | ) 269 | (symbol "uConsoleSpeaker_1_1" 270 | (pin bidirectional line (at -15.24 -3.81 0) (length 5.08) 271 | (name "P1" (effects (font (size 1.27 1.27)))) 272 | (number "1" (effects (font (size 1.27 1.27)))) 273 | ) 274 | (pin bidirectional line (at -15.24 -11.43 0) (length 5.08) 275 | (name "N1" (effects (font (size 1.27 1.27)))) 276 | (number "2" (effects (font (size 1.27 1.27)))) 277 | ) 278 | (pin bidirectional line (at 15.24 -11.43 180) (length 5.08) 279 | (name "P2" (effects (font (size 1.27 1.27)))) 280 | (number "3" (effects (font (size 1.27 1.27)))) 281 | ) 282 | (pin bidirectional line (at 15.24 -3.81 180) (length 5.08) 283 | (name "N2" (effects (font (size 1.27 1.27)))) 284 | (number "4" (effects (font (size 1.27 1.27)))) 285 | ) 286 | ) 287 | ) 288 | ) 289 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## μPico 2 | 3 | 4 | 5 | ### What is it? 6 | 7 | uPico is a [RP2040](https://www.raspberrypi.com/products/rp2040/) powered expansion card designed to enhance the capabilities of [Clockwork's uConsole](https://www.clockworkpi.com/uconsole). 8 | 9 | ⚠️ Only R-01 and CM4 core are supported by control application. 10 | Work in progress for A04/A06 core modules. 11 | 12 | Second project name is `atto`, cause 10−6 * 10-12 = 10-18 🤓 13 | 14 | ### Features 15 | 16 | * Internal Speakers Support 17 | * Type-C (USB 2.0) port with programmable power switch and overcurrent protection 18 | * 3.3V and 5V external power out with programmable switch and overcurrent protection 19 | * RP2040 with extenal double-double PMOD compatible connector 20 | * RP2040 controllable LED 21 | 22 | ## Resources 23 | 24 | - [Schematics](docs/upico.pdf) 25 | - [PCB Viewer](https://kicanvas.org/?github=https%3A%2F%2Fgithub.com%2Fdotcypress%2Fupico%2Fblob%2Fmain%2Fpcb%2Fupico.kicad_pcb) 26 | - [Interactive BOM](https://htmlpreview.github.io/?https://github.com/dotcypress/upico/blob/main/docs/ibom.html) 27 | 28 | I sell on Tindie 29 | 30 | ### Control app installation 31 | 32 | 1. Download latest build from [Releases page](https://github.com/dotcypress/upico/releases) 33 | 2. Extract installer: `mkdir dist && tar -xzf upico_%version%.%core%.tar.gz -C dist` 34 | 3. Install: `cd dist && sudo ./install.sh` 35 | 4. Cleanup: `cd .. && rm -rf dist` 36 | 5. Print help: `upico help` 37 | 38 | ### Building control app from sources 39 | 40 | 1. Install rustup by following the instructions at https://rustup.rs 41 | 2. Clone this repo: `git clone git@github.com:dotcypress/upico.git && cd upico` 42 | 3. Build: `cargo build --release` 43 | 4. Install app: `sudo cp target/release/upico /usr/local/bin/` 44 | 5. Install service: `sudo cp upico.service /etc/systemd/system/` 45 | 6. Enable service: `sudo systemctl enable upico` 46 | 7. Start service: `sudo systemctl start upico` 47 | 8. Setup uPico extender USB device: `echo 'SUBSYSTEM=="usb",ATTRS{idVendor}=="1209",ATTRS{idProduct}=="bc07",MODE="0660",GROUP="plugdev"' > /etc/udev/rules.d/50-upico-permissions.rules` 48 | 9. Reload udev: `udevadm control --reload-rules` 49 | 10. Print help: `upico help` 50 | 51 | ### Flash firmware 52 | 53 | 1. `wget https://rptl.io/pico-blink` 54 | 2. `upico install pico-blink` or `upico install -m pico-blink` if automount is disabled for hot-plug devices. 55 | 56 | See other examples: https://github.com/raspberrypi/pico-examples 57 | 58 | ### High level design diagram 59 | 60 | 61 | 62 | ### GPIO Header Pinout 63 | ``` 64 | ╔══════╦══════╗ 65 | ║ AUX ║ AUX ║ 66 | ╠══════╬══════╣ 67 | ║ VDD ║ VDD ║ 68 | ║ GND ║ GND ║ 69 | ║ IO3 ║ IO7 ║ 70 | ║ IO2 ║ IO6 ║ 71 | ║ IO1 ║ IO5 ║ 72 | ║ IO0 ║ IO4 ║ 73 | ╠══════╬══════╣ 74 | ║ IO27 ║ IO29 ║ 75 | ║ IO26 ║ IO28 ║ 76 | ║ IO18 ║ IO19 ║ 77 | ╠══════╬══════╣ 78 | ║ VDD ║ VDD ║ 79 | ║ GND ║ GND ║ 80 | ║ IO11 ║ IO15 ║ 81 | ║ IO10 ║ IO14 ║ 82 | ║ IO9 ║ IO13 ║ 83 | ║ IO8 ║ IO12 ║ 84 | ╚══════╩══════╝ 85 | ``` 86 | 87 | ## Errata 88 | 89 | ### CM4 core & uPico PCB rev:0x02 90 | 91 | * Overcurrent reporting feature isn't supported. 92 | 93 | ## License 94 | 95 | Licensed under either of 96 | 97 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 98 | http://www.apache.org/licenses/LICENSE-2.0) 99 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 100 | 101 | at your option. 102 | 103 | ### Contribution 104 | 105 | Unless you explicitly state otherwise, any contribution intentionally submitted 106 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 107 | dual licensed as above, without any additional terms or conditions. 108 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "r01")] 2 | pub mod platform { 3 | pub const OCP_REPORTING: bool = true; 4 | pub const AUX_SWITCH: bool = true; 5 | pub const PIN_PICO_BOOT: usize = 37; 6 | pub const PIN_VDD_EN: usize = 36; 7 | pub const PIN_USB_EN: usize = 31; 8 | pub const PIN_PICO_RUN: usize = 38; 9 | pub const PIN_AUX_EN: usize = 40; 10 | pub const PIN_AUX_OCP: usize = 39; 11 | pub const PIN_VDD_OCP: usize = 35; 12 | pub const PIN_USB_OCP: usize = 30; 13 | } 14 | 15 | #[cfg(feature = "cm4")] 16 | pub mod platform { 17 | pub const AUX_SWITCH: bool = true; 18 | pub const OCP_REPORTING: bool = false; 19 | pub const PIN_PICO_BOOT: usize = 27; 20 | pub const PIN_VDD_EN: usize = 26; 21 | pub const PIN_USB_EN: usize = 21; 22 | pub const PIN_PICO_RUN: usize = 6; 23 | pub const PIN_AUX_EN: usize = 16; 24 | //TODO: fix pcb routing 25 | pub const PIN_AUX_OCP: usize = 29; 26 | pub const PIN_VDD_OCP: usize = 25; 27 | pub const PIN_USB_OCP: usize = 20; 28 | } 29 | 30 | #[cfg(feature = "cm4-bookworm")] 31 | pub mod platform { 32 | pub const AUX_SWITCH: bool = true; 33 | pub const OCP_REPORTING: bool = true; 34 | pub const PIN_PICO_BOOT: usize = 2; 35 | pub const PIN_VDD_EN: usize = 25; 36 | pub const PIN_USB_EN: usize = 29; 37 | pub const PIN_PICO_RUN: usize = 22; 38 | pub const PIN_AUX_EN: usize = 27; 39 | pub const PIN_AUX_OCP: usize = 11; 40 | pub const PIN_VDD_OCP: usize = 6; 41 | pub const PIN_USB_OCP: usize = 28; 42 | } 43 | 44 | #[cfg(feature = "a06")] 45 | pub mod platform { 46 | pub const OCP_REPORTING: bool = false; 47 | pub const AUX_SWITCH: bool = false; 48 | pub const PIN_PICO_BOOT: usize = 37; 49 | pub const PIN_VDD_EN: usize = 36; 50 | pub const PIN_USB_EN: usize = 31; 51 | //TODO: fix pcb routing 52 | pub const PIN_PICO_RUN: usize = 42; 53 | pub const PIN_AUX_EN: usize = 40; 54 | pub const PIN_AUX_OCP: usize = 36; 55 | pub const PIN_VDD_OCP: usize = 35; 56 | pub const PIN_USB_OCP: usize = 30; 57 | } 58 | 59 | #[cfg(feature = "a04")] 60 | pub mod platform { 61 | pub const OCP_REPORTING: bool = todo!(); 62 | pub const AUX_SWITCH: bool = todo!(); 63 | pub const PIN_PICO_BOOT: usize = todo!(); 64 | pub const PIN_VDD_EN: usize = todo!(); 65 | pub const PIN_USB_EN: usize = todo!(); 66 | pub const PIN_PICO_RUN: usize = todo!(); 67 | pub const PIN_AUX_EN: usize = todo!(); 68 | pub const PIN_AUX_OCP: usize = todo!(); 69 | pub const PIN_VDD_OCP: usize = todo!(); 70 | pub const PIN_USB_OCP: usize = todo!(); 71 | } 72 | -------------------------------------------------------------------------------- /src/extender.rs: -------------------------------------------------------------------------------- 1 | use rusb::*; 2 | use std::time::Duration; 3 | 4 | pub struct GpioState { 5 | levels: u32, 6 | pin_dirs: u32, 7 | } 8 | 9 | impl GpioState { 10 | pub fn new(levels: u32, pin_dirs: u32) -> Self { 11 | Self { levels, pin_dirs } 12 | } 13 | 14 | pub fn get_mode(&self, pin: u8) -> bool { 15 | (self.pin_dirs >> pin) & 1 == 1 16 | } 17 | 18 | pub fn get_level(&self, pin: u8) -> bool { 19 | (self.levels >> pin) & 1 == 1 20 | } 21 | 22 | pub fn set_mode(&mut self, pin: u8, mode: bool) { 23 | if mode { 24 | self.pin_dirs |= 1 << pin; 25 | } else { 26 | self.pin_dirs &= !(1 << pin); 27 | } 28 | } 29 | 30 | pub fn set_level(&mut self, pin: u8, level: bool) { 31 | if level { 32 | self.levels |= 1 << pin; 33 | } else { 34 | self.levels &= !(1 << pin); 35 | } 36 | } 37 | } 38 | 39 | pub struct Extender; 40 | 41 | impl Extender { 42 | pub fn read_analog() -> rusb::Result<[u16; 4]> { 43 | let dev = Self::open_device()?; 44 | let mut scratch = [0; 8]; 45 | let req_type = request_type(Direction::In, RequestType::Vendor, Recipient::Device); 46 | dev.read_control( 47 | req_type, 48 | 0x01, 49 | 0x00, 50 | 0x00, 51 | &mut scratch, 52 | Duration::from_millis(100), 53 | )?; 54 | Ok([ 55 | u16::from_le_bytes(scratch[0..2].try_into().unwrap()), 56 | u16::from_le_bytes(scratch[2..4].try_into().unwrap()), 57 | u16::from_le_bytes(scratch[4..6].try_into().unwrap()), 58 | u16::from_le_bytes(scratch[6..8].try_into().unwrap()), 59 | ]) 60 | } 61 | 62 | pub fn read_digital() -> rusb::Result { 63 | let dev = Self::open_device()?; 64 | let mut scratch = [0; 8]; 65 | let req_type = request_type(Direction::In, RequestType::Vendor, Recipient::Device); 66 | dev.read_control( 67 | req_type, 68 | 0x00, 69 | 0x00, 70 | 0x00, 71 | &mut scratch, 72 | Duration::from_millis(100), 73 | )?; 74 | let res = GpioState::new( 75 | u32::from_le_bytes(scratch[0..4].try_into().unwrap()), 76 | u32::from_le_bytes(scratch[4..8].try_into().unwrap()), 77 | ); 78 | Ok(res) 79 | } 80 | 81 | pub fn write_digital(state: GpioState) -> rusb::Result<()> { 82 | let dev = Self::open_device()?; 83 | let mut payload = [0; 8]; 84 | payload[0..4].copy_from_slice(&state.levels.to_le_bytes()); 85 | payload[4..8].copy_from_slice(&state.pin_dirs.to_le_bytes()); 86 | let req_type = request_type(Direction::Out, RequestType::Vendor, Recipient::Device); 87 | dev.write_control( 88 | req_type, 89 | 0x00, 90 | 0x00, 91 | 0x00, 92 | &payload, 93 | Duration::from_millis(100), 94 | )?; 95 | Ok(()) 96 | } 97 | 98 | pub fn set_led(on: bool) -> rusb::Result<()> { 99 | let dev = Self::open_device()?; 100 | let req_type = request_type(Direction::Out, RequestType::Vendor, Recipient::Device); 101 | dev.write_control( 102 | req_type, 103 | 0x01, 104 | on as _, 105 | 0x00, 106 | &[], 107 | Duration::from_millis(100), 108 | )?; 109 | Ok(()) 110 | } 111 | 112 | fn open_device() -> rusb::Result> { 113 | rusb::open_device_with_vid_pid(0x1209, 0xbc07).ok_or(rusb::Error::NoDevice) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/gpio.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::time::Duration; 3 | 4 | pub struct Gpio {} 5 | 6 | impl Gpio { 7 | pub fn try_new() -> Result { 8 | Self::set_pin_mode_out(platform::PIN_PICO_RUN, true)?; 9 | Self::set_pin_mode_out(platform::PIN_PICO_BOOT, true)?; 10 | Self::set_pin_mode_out(platform::PIN_USB_EN, false)?; 11 | Self::set_pin_mode_out(platform::PIN_VDD_EN, false)?; 12 | if platform::AUX_SWITCH { 13 | Self::set_pin_mode_out(platform::PIN_AUX_EN, false)?; 14 | } 15 | if platform::OCP_REPORTING { 16 | Self::set_pin_mode_in(platform::PIN_AUX_OCP)?; 17 | Self::set_pin_mode_in(platform::PIN_VDD_OCP)?; 18 | Self::set_pin_mode_in(platform::PIN_USB_OCP)?; 19 | } 20 | Ok(Self {}) 21 | } 22 | 23 | pub fn reset_pico(&mut self, boot: bool) -> Result<(), io::Error> { 24 | Self::set_pin_state(platform::PIN_VDD_EN, true)?; 25 | Self::set_pin_state(platform::PIN_PICO_RUN, false)?; 26 | Self::set_pin_state(platform::PIN_PICO_BOOT, !boot)?; 27 | thread::sleep(Duration::from_millis(200)); 28 | Self::set_pin_state(platform::PIN_VDD_EN, false)?; 29 | Self::set_pin_state(platform::PIN_PICO_RUN, true)?; 30 | if boot { 31 | thread::sleep(Duration::from_millis(100)); 32 | Self::set_pin_state(platform::PIN_PICO_BOOT, true)?; 33 | } 34 | Ok(()) 35 | } 36 | 37 | pub fn set_power_enabled(&mut self, line: PowerLine, enabled: bool) -> Result<(), io::Error> { 38 | match line { 39 | PowerLine::Vdd => Self::set_pin_state(platform::PIN_VDD_EN, !enabled), 40 | PowerLine::Usb => Self::set_pin_state(platform::PIN_USB_EN, !enabled), 41 | PowerLine::Aux if platform::AUX_SWITCH => { 42 | Self::set_pin_state(platform::PIN_AUX_EN, !enabled) 43 | } 44 | _ => Ok(()), 45 | } 46 | } 47 | 48 | pub fn power_cycle(&mut self, line: PowerLine) -> Result<(), io::Error> { 49 | self.set_power_enabled(line, false)?; 50 | thread::sleep(Duration::from_millis(100)); 51 | self.set_power_enabled(line, true) 52 | } 53 | 54 | pub fn power_report(&mut self) -> Result { 55 | Ok(PowerReport { 56 | aux: PowerState { 57 | on: !Self::get_pin_state(platform::PIN_AUX_EN)?, 58 | ocp: !Self::get_pin_state(platform::PIN_AUX_OCP)?, 59 | }, 60 | vdd: PowerState { 61 | on: !Self::get_pin_state(platform::PIN_VDD_EN)?, 62 | ocp: !Self::get_pin_state(platform::PIN_VDD_OCP)?, 63 | }, 64 | usb: PowerState { 65 | on: !Self::get_pin_state(platform::PIN_USB_EN)?, 66 | ocp: !Self::get_pin_state(platform::PIN_USB_OCP)?, 67 | }, 68 | }) 69 | } 70 | 71 | fn set_pin_mode_out(pin: usize, def_state: bool) -> Result<(), io::Error> { 72 | process::Command::new("gpio") 73 | .args(["mode", &pin.to_string(), "out"]) 74 | .output()?; 75 | Self::set_pin_state(pin, def_state) 76 | } 77 | 78 | fn set_pin_mode_in(pin: usize) -> Result<(), io::Error> { 79 | process::Command::new("gpio") 80 | .args(["mode", &pin.to_string(), "in"]) 81 | .output()?; 82 | Ok(()) 83 | } 84 | 85 | fn get_pin_state(pin: usize) -> Result { 86 | let stdout = process::Command::new("gpio") 87 | .args(["read", &pin.to_string()]) 88 | .stdout(process::Stdio::piped()) 89 | .output()? 90 | .stdout; 91 | Ok(!stdout.is_empty() && stdout[0] == b'1') 92 | } 93 | 94 | fn set_pin_state(pin: usize, state: bool) -> Result<(), io::Error> { 95 | process::Command::new("gpio") 96 | .args(["write", &pin.to_string(), if state { "1" } else { "0" }]) 97 | .output()?; 98 | Ok(()) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{builder::PossibleValue, *}; 2 | use clap_complete::{generate, Shell}; 3 | use config::*; 4 | use extender::*; 5 | use gpio::*; 6 | use service::*; 7 | use std::path::Path; 8 | use std::time::Duration; 9 | use std::*; 10 | 11 | mod config; 12 | mod extender; 13 | mod gpio; 14 | mod service; 15 | 16 | #[derive(Debug)] 17 | pub enum AppError { 18 | InvalidLine, 19 | InvalidGpioLine, 20 | InvalidAdcChannel, 21 | InvalidLedMode, 22 | MountFailed, 23 | IoError(io::Error), 24 | ServiceError(io::Error), 25 | GpioError(io::Error), 26 | DecodeError(string::FromUtf8Error), 27 | ParseIntError(num::ParseIntError), 28 | ProtocolError(rmp_serde::decode::Error), 29 | UsbError(rusb::Error), 30 | } 31 | 32 | pub type AppResult = Result<(), AppError>; 33 | 34 | fn main() { 35 | if let Err(err) = run() { 36 | match err { 37 | AppError::InvalidLine => println!("Invalid power line name"), 38 | AppError::InvalidAdcChannel => println!("Invalid ADC channel"), 39 | AppError::InvalidLedMode => println!("Invalid LED mode"), 40 | AppError::InvalidGpioLine => println!("Invalid GPIO number"), 41 | AppError::MountFailed => println!("Failed to mount Pico drive"), 42 | AppError::GpioError(err) => println!("GPIO error: {}", err), 43 | AppError::IoError(err) => println!("IO error: {}", err), 44 | AppError::ServiceError(err) => println!("Service error: {}", err), 45 | AppError::DecodeError(err) => println!("Decode error: {}", err), 46 | AppError::ProtocolError(err) => println!("Protocol error: {}", err), 47 | AppError::UsbError(rusb::Error::NoDevice) => println!("Pico extender not found.\nCommand for flashing extender firmware: \"upico gpio install\"."), 48 | AppError::UsbError(err) => println!("USB error: {}", err), 49 | AppError::ParseIntError(err) => println!("Parse error: {}", err), 50 | }; 51 | } 52 | } 53 | 54 | fn cli() -> Command { 55 | let mount_arg = arg!(mount: -m "Mount Pico disk"); 56 | let dev_arg = arg!(-d "Path to Pico disk device").default_value("/dev/sda1"); 57 | let line_arg = arg!( "Power line").required(true); 58 | let line_arg = if platform::AUX_SWITCH { 59 | line_arg.value_parser([ 60 | PossibleValue::new("aux"), 61 | PossibleValue::new("vdd"), 62 | PossibleValue::new("usb"), 63 | ]) 64 | } else { 65 | line_arg.value_parser([PossibleValue::new("vdd"), PossibleValue::new("usb")]) 66 | }; 67 | 68 | let mount_path: &'static str = { 69 | let username = env::var("USER").unwrap_or("pi".into()); 70 | Box::leak(format!("/media/{username}/RPI-RP2").into_boxed_str()) 71 | }; 72 | 73 | Command::new("upico") 74 | .about("uPico control app") 75 | .version(env!("CARGO_PKG_VERSION")) 76 | .author(env!("CARGO_PKG_AUTHORS")) 77 | .subcommand_required(true) 78 | .arg_required_else_help(true) 79 | .subcommand(Command::new("service").about("Start service").hide(true)) 80 | .subcommand(Command::new("reset").about("Reset Pico")) 81 | .subcommand( 82 | Command::new("boot") 83 | .arg(mount_arg.clone()) 84 | .arg(dev_arg.clone()) 85 | .about("Reset Pico and enter USB bootloader"), 86 | ) 87 | .subcommand( 88 | Command::new("generate") 89 | .arg( 90 | Arg::new("generator") 91 | .required(true) 92 | .value_parser(value_parser!(Shell)), 93 | ) 94 | .about("Generate shell completions"), 95 | ) 96 | .subcommand( 97 | Command::new("gpio") 98 | .about("GPIO utils") 99 | .subcommand_required(true) 100 | .arg_required_else_help(true) 101 | .subcommand( 102 | Command::new("get") 103 | .arg(arg!([PIN] "GPIO pin number (0-15).")) 104 | .about("Print GPIO status"), 105 | ) 106 | .subcommand( 107 | Command::new("set") 108 | .arg( 109 | arg!( "Comma separated GPIO config (0=1,3=0,7=i,..).") 110 | .value_delimiter(',') 111 | .required(true), 112 | ) 113 | .about("Set GPIO config"), 114 | ) 115 | .subcommand( 116 | Command::new("led") 117 | .arg(arg!( "LED status (on, off).").required(true)) 118 | .about("Set LED status"), 119 | ) 120 | .subcommand( 121 | Command::new("install") 122 | .arg( 123 | arg!(-p "Path to mounted Pico disk") 124 | .default_value(mount_path), 125 | ) 126 | .arg(mount_arg.clone()) 127 | .arg(dev_arg.clone()) 128 | .about("Install GPIO extender firmware to Pico"), 129 | ), 130 | ) 131 | .subcommand( 132 | Command::new("install") 133 | .about("Install firmware to Pico") 134 | .arg_required_else_help(true) 135 | .arg(arg!( "Path to UF2 firmware file").required(true)) 136 | .arg(arg!(-p "Path to mounted Pico disk").default_value(mount_path)) 137 | .arg(mount_arg) 138 | .arg(dev_arg), 139 | ) 140 | .subcommand( 141 | Command::new("power") 142 | .about("Power management") 143 | .subcommand_required(true) 144 | .arg_required_else_help(true) 145 | .subcommand(Command::new("on").about("Power on").arg(line_arg.clone())) 146 | .subcommand(Command::new("off").about("Power off").arg(line_arg.clone())) 147 | .subcommand(Command::new("cycle").about("Power cycle").arg(line_arg)) 148 | .subcommand( 149 | Command::new("status") 150 | .about("Print power status") 151 | .hide(!platform::OCP_REPORTING), 152 | ), 153 | ) 154 | .subcommand( 155 | Command::new("pinout") 156 | .arg(arg!(full: -f "Print pin functions")) 157 | .about("Print pinout diagram"), 158 | ) 159 | } 160 | 161 | fn print_power_state(line: &str, state: PowerState) { 162 | println!( 163 | "{line}: {} {}", 164 | if state.on { "ON " } else { "OFF" }, 165 | if platform::OCP_REPORTING && state.ocp { 166 | "[OCP]" 167 | } else { 168 | "" 169 | } 170 | ); 171 | } 172 | 173 | fn parse_power_line(args: &ArgMatches) -> Result { 174 | args.get_one::("LINE") 175 | .unwrap() 176 | .try_into() 177 | .map_err(|_| AppError::InvalidLine) 178 | } 179 | 180 | fn sleep(millis: u64) { 181 | thread::sleep(Duration::from_millis(millis)); 182 | } 183 | 184 | fn wait_for_path(path: &Path) { 185 | for _ in 0..100 { 186 | if path.exists() { 187 | sleep(200); 188 | break; 189 | } 190 | sleep(100); 191 | } 192 | } 193 | 194 | fn mount_pico(disk: &str) -> Result { 195 | wait_for_path(Path::new(disk)); 196 | for _ in 0..50 { 197 | if let Ok(output) = process::Command::new("udisksctl") 198 | .args(["mount", "-b", disk]) 199 | .stdout(process::Stdio::piped()) 200 | .output() 201 | { 202 | if output.status.success() { 203 | let res = String::from_utf8(output.stdout).map_err(AppError::DecodeError)?; 204 | return res 205 | .split(" at ") 206 | .last() 207 | .map(|s| s.trim().to_owned()) 208 | .ok_or(AppError::MountFailed); 209 | } 210 | sleep(100); 211 | } 212 | } 213 | Err(AppError::MountFailed) 214 | } 215 | 216 | fn run() -> AppResult { 217 | match cli().get_matches().subcommand() { 218 | Some(("service", _)) => Service::start()?, 219 | Some(("generate", args)) => { 220 | if let Some(generator) = args.get_one::("generator") { 221 | generate(*generator, &mut cli(), "upico", &mut io::stdout()); 222 | } 223 | } 224 | Some(("pinout", args)) => { 225 | if args.get_flag("full") { 226 | println!("{}", include_str!("resources/pinout_full.ansi")); 227 | } else { 228 | println!("{}", include_str!("resources/pinout.ansi")); 229 | } 230 | } 231 | Some(("reset", _)) => { 232 | Service::send(Request::Reset)?; 233 | } 234 | Some(("boot", args)) => { 235 | Service::send(Request::EnterBootloader)?; 236 | if args.get_flag("mount") { 237 | let disk = args.get_one::("PICO_DEV").unwrap(); 238 | mount_pico(disk)?; 239 | } 240 | } 241 | Some(("install", args)) => { 242 | Service::send(Request::EnterBootloader)?; 243 | let mut path = if args.get_flag("mount") { 244 | let disk = args.get_one::("PICO_DEV").unwrap(); 245 | mount_pico(disk)? 246 | } else { 247 | let path = args.get_one::("PICO_PATH").unwrap().to_string(); 248 | wait_for_path(Path::new(&path)); 249 | path 250 | }; 251 | path.push_str("/fw.uf2"); 252 | let firmware = args.get_one::("FIRMWARE").unwrap(); 253 | fs::copy(firmware, path).map_err(AppError::IoError)?; 254 | } 255 | Some(("power", args)) => match args.subcommand() { 256 | Some(("on", args)) => { 257 | let line = parse_power_line(args)?; 258 | Service::send(Request::PowerOn(line))?; 259 | } 260 | Some(("off", args)) => { 261 | let line = parse_power_line(args)?; 262 | Service::send(Request::PowerOff(line))?; 263 | } 264 | Some(("cycle", args)) => { 265 | let line = parse_power_line(args)?; 266 | Service::send(Request::PowerCycle(line))?; 267 | } 268 | Some(("status", _)) => { 269 | if let Response::PowerReport(report) = Service::send(Request::PowerStatus)? { 270 | if platform::AUX_SWITCH { 271 | print_power_state("AUX", report.aux); 272 | } 273 | print_power_state("VDD", report.vdd); 274 | print_power_state("USB", report.usb); 275 | } 276 | } 277 | _ => {} 278 | }, 279 | Some(("gpio", args)) => match args.subcommand() { 280 | Some(("set", args)) => { 281 | let mut gpio_state = Extender::read_digital().map_err(AppError::UsbError)?; 282 | if let Some(configs) = args.get_many::("CONFIG") { 283 | for pin_config in configs { 284 | if let Some((pin, mode)) = pin_config.split_once('=') { 285 | let pin: u8 = pin.parse().map_err(AppError::ParseIntError)?; 286 | if pin >= 16 { 287 | return Err(AppError::InvalidGpioLine); 288 | } 289 | match mode { 290 | "i" => gpio_state.set_mode(pin, false), 291 | "0" => { 292 | gpio_state.set_mode(pin, true); 293 | gpio_state.set_level(pin, false); 294 | } 295 | "1" => { 296 | gpio_state.set_mode(pin, true); 297 | gpio_state.set_level(pin, true); 298 | } 299 | _ => {} 300 | } 301 | } 302 | } 303 | } 304 | Extender::write_digital(gpio_state).map_err(AppError::UsbError)?; 305 | } 306 | Some(("get", args)) => { 307 | if let Some(pin) = args 308 | .get_one::("PIN") 309 | .map(|s| s.parse::().unwrap_or_default()) 310 | { 311 | match pin { 312 | 0..=15 => { 313 | let gpio_state = 314 | Extender::read_digital().map_err(AppError::UsbError)?; 315 | let level = if gpio_state.get_level(pin) { "1" } else { "0" }; 316 | println!("{}", level); 317 | } 318 | 26..=29 => { 319 | let values = Extender::read_analog().map_err(AppError::UsbError)?; 320 | println!("{}", values[pin as usize - 26]); 321 | } 322 | _ => return Err(AppError::InvalidGpioLine), 323 | } 324 | } else { 325 | let gpio_state = Extender::read_digital().map_err(AppError::UsbError)?; 326 | for pin in 0..16 { 327 | let level = if gpio_state.get_level(pin) { "1" } else { "0" }; 328 | let mode = if gpio_state.get_mode(pin) { 329 | "Output" 330 | } else { 331 | "Input" 332 | }; 333 | println!("GPIO{}\t{}\t{}", pin, mode, level); 334 | } 335 | let values = Extender::read_analog().map_err(AppError::UsbError)?; 336 | for (idx, val) in values.iter().enumerate() { 337 | println!("GPIO{}\tAnalog\t{}", idx + 26, val); 338 | } 339 | } 340 | } 341 | Some(("led", args)) => match args.get_one::("STATUS") { 342 | Some(status) => match status.as_str() { 343 | "on" => Extender::set_led(true).map_err(AppError::UsbError)?, 344 | "off" => Extender::set_led(false).map_err(AppError::UsbError)?, 345 | _ => return Err(AppError::InvalidLedMode), 346 | }, 347 | _ => return Err(AppError::InvalidLedMode), 348 | }, 349 | 350 | Some(("install", args)) => { 351 | Service::send(Request::EnterBootloader)?; 352 | let mut path = if args.get_flag("mount") { 353 | let disk = args.get_one::("PICO_DEV").unwrap(); 354 | mount_pico(disk)? 355 | } else { 356 | let path = args.get_one::("PICO_PATH").unwrap().to_string(); 357 | wait_for_path(Path::new(&path)); 358 | path 359 | }; 360 | path.push_str("/fw.uf2"); 361 | fs::write(path, include_bytes!("resources/extender.uf2")) 362 | .map_err(AppError::IoError)?; 363 | } 364 | _ => {} 365 | }, 366 | _ => {} 367 | } 368 | 369 | Ok(()) 370 | } 371 | -------------------------------------------------------------------------------- /src/resources/extender.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcypress/upico/9b27084063b062241dfb2fffc1fdef4a609679d1/src/resources/extender.uf2 -------------------------------------------------------------------------------- /src/resources/pinout.ansi: -------------------------------------------------------------------------------- 1 | ╔══════╦══════╗ 2 | ║ 5V ║ 5V ║ 3 | ╠══════╬══════╣ 4 | ║ VDD ║ VDD ║ 5 | ║ GND ║ GND ║ 6 | ║ IO3 ║ IO7 ║ 7 | ║ IO2 ║ IO6 ║ 8 | ║ IO1 ║ IO5 ║ 9 | ║ IO0 ║ IO4 ║ 10 | ╠══════╬══════╣ 11 | ║ IO27 ║ IO29 ║ 12 | ║ IO26 ║ IO28 ║ 13 | ║ IO18 ║ IO19 ║ 14 | ╠══════╬══════╣ 15 | ║ VDD ║ VDD ║ 16 | ║ GND ║ GND ║ 17 | ║ IO11 ║ IO15 ║ 18 | ║ IO10 ║ IO14 ║ 19 | ║ IO9 ║ IO13 ║ 20 | ║ IO8 ║ IO12 ║ 21 | ╚══════╩══════╝ -------------------------------------------------------------------------------- /src/resources/pinout_full.ansi: -------------------------------------------------------------------------------- 1 | ══════════════════════════════════════════╦══════════════════════════════════════════ 2 | (5V) AUX - 1║2 - AUX (5V) 3 | ══════════════════════════════════════════╬══════════════════════════════════════════ 4 | (3.3V) VDD - 3║4 - VDD (3.3V) 5 | GND - 5║6 - GND 6 | I2C1 SCL - SPI0 TX - IO3 - 7║8 - IO7 - SPI0 TX - I2C1 SCL 7 | I2C1 SDA - SPI0 SCK - IO2 - 9║10 - IO6 - SPI0 SCK - I2C1 SDA 8 | UART0 RX - I2C0 SCL - SPI0 CSn - IO1 - 11║12 - IO5 - SPI0 CSn - I2C0 SCL - UART1 RX 9 | UART0 TX - I2C0 SDA - SPI0 RX - IO0 - 13║14 - IO4 - SPI0 RX - I2C0 SDA - UART1 TX 10 | ══════════════════════════════════════════╬══════════════════════════════════════════ 11 | I2C1 SCL - ADC1 - IO27 - 15║16 - IO29 - ADC3 / ADC_VREF 12 | I2C1 SDA - ADC0 - IO26 - 17║18 - IO28 - ADC2 13 | I2C1 SDA - SPI0 SCK - IO18 - 19║20 - IO19 - SPI0 TX - I2C1 SCL 14 | ══════════════════════════════════════════╬══════════════════════════════════════════ 15 | (3.3V) VDD - 21║22 - VDD (3.3V) 16 | GND - 23║24 - GND 17 | I2C1 SCL - SPI1 TX - IO11 - 25║26 - IO15 - SPI1 TX - I2C1 SCL 18 | I2C1 SDA - SPI1 SCK - IO10 - 27║28 - IO14 - SPI1 SCK - I2C1 SDA 19 | UART1 RX - I2C0 SCL - SPI1 CSn - IO9 - 29║30 - IO13 - SPI1 CSn - I2C0 SCL - UART0 RX 20 | UART1 TX - I2C0 SDA - SPI1 RX - IO8 - 31║32 - IO12 - SPI1 RX - I2C0 SDA - UART0 TX 21 | ══════════════════════════════════════════╩══════════════════════════════════════════ -------------------------------------------------------------------------------- /src/service.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use rmp_serde::*; 3 | use serde::*; 4 | use std::{ 5 | io::{ErrorKind, Read, Write}, 6 | os::unix::{net::*, prelude::PermissionsExt}, 7 | time::Duration, 8 | }; 9 | 10 | #[derive(Serialize, Deserialize, Debug, Copy, Clone)] 11 | pub enum PowerLine { 12 | Aux, 13 | Vdd, 14 | Usb, 15 | } 16 | 17 | impl TryFrom<&String> for PowerLine { 18 | type Error = (); 19 | 20 | fn try_from(value: &String) -> Result { 21 | let line = match value.to_lowercase().as_str() { 22 | "aux" => Self::Aux, 23 | "vdd" => Self::Vdd, 24 | "usb" => Self::Usb, 25 | _ => return Err(()), 26 | }; 27 | Ok(line) 28 | } 29 | } 30 | 31 | #[derive(Serialize, Deserialize, Debug, Copy, Clone)] 32 | pub struct PowerState { 33 | pub on: bool, 34 | pub ocp: bool, 35 | } 36 | 37 | #[derive(Serialize, Deserialize, Debug, Copy, Clone)] 38 | pub struct PowerReport { 39 | pub aux: PowerState, 40 | pub vdd: PowerState, 41 | pub usb: PowerState, 42 | } 43 | 44 | #[derive(Serialize, Deserialize, Debug, Clone)] 45 | pub enum Request { 46 | Reset, 47 | EnterBootloader, 48 | PowerStatus, 49 | PowerOn(PowerLine), 50 | PowerCycle(PowerLine), 51 | PowerOff(PowerLine), 52 | } 53 | 54 | #[derive(Serialize, Deserialize, Debug, Copy, Clone)] 55 | pub enum Response { 56 | Done, 57 | ServiceError, 58 | PowerReport(PowerReport), 59 | } 60 | 61 | pub struct Service { 62 | gpio: Gpio, 63 | } 64 | 65 | impl Service { 66 | const SOCKET: &'static str = "/tmp/upico.sock"; 67 | 68 | pub fn start() -> AppResult { 69 | let err = UnixStream::connect(Service::SOCKET).map_err(|err| err.kind()); 70 | if let Err(ErrorKind::ConnectionRefused) = err { 71 | fs::remove_file(Service::SOCKET).map_err(AppError::ServiceError)? 72 | } 73 | let listener = UnixListener::bind(Service::SOCKET).map_err(AppError::ServiceError)?; 74 | let mut perms = fs::metadata(Service::SOCKET) 75 | .map_err(AppError::IoError)? 76 | .permissions(); 77 | perms.set_mode(0o766); 78 | fs::set_permissions(Service::SOCKET, perms).map_err(AppError::IoError)?; 79 | 80 | let gpio = Gpio::try_new().map_err(AppError::GpioError)?; 81 | let mut service = Self { gpio }; 82 | 83 | let mut scratch = [0; 64]; 84 | for mut stream in listener.incoming().flatten() { 85 | stream 86 | .read(&mut scratch) 87 | .map_err(AppError::ServiceError) 88 | .and_then(|n| { 89 | from_slice::(&scratch[0..n]).map_err(AppError::ProtocolError) 90 | }) 91 | .map(|req| service.on_request(req).unwrap_or(Response::ServiceError)) 92 | .map(|res| to_vec(&res).unwrap()) 93 | .map(|packet| stream.write(&packet)) 94 | .ok(); 95 | } 96 | Ok(()) 97 | } 98 | 99 | pub fn send(req: Request) -> Result { 100 | let mut stream = UnixStream::connect(Service::SOCKET).map_err(AppError::ServiceError)?; 101 | 102 | let packet = to_vec(&req).unwrap(); 103 | stream.write(&packet).map_err(AppError::IoError)?; 104 | thread::sleep(Duration::from_millis(300)); 105 | 106 | let mut scratch = [0; 64]; 107 | let n = stream.read(&mut scratch).map_err(AppError::ServiceError)?; 108 | from_slice(&scratch[0..n]).map_err(AppError::ProtocolError) 109 | } 110 | 111 | fn on_request(&mut self, req: Request) -> Result { 112 | match req { 113 | Request::PowerOn(line) => self.gpio.set_power_enabled(line, true)?, 114 | Request::PowerOff(line) => self.gpio.set_power_enabled(line, false)?, 115 | Request::PowerCycle(line) => self.gpio.power_cycle(line)?, 116 | Request::Reset => self.gpio.reset_pico(false)?, 117 | Request::EnterBootloader => self.gpio.reset_pico(true)?, 118 | Request::PowerStatus => { 119 | let report = self.gpio.power_report()?; 120 | return Ok(Response::PowerReport(report)); 121 | } 122 | } 123 | Ok(Response::Done) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /upico.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=upico 3 | 4 | [Service] 5 | Type=simple 6 | ExecStart=/usr/local/bin/upico service 7 | 8 | [Install] 9 | WantedBy=multi-user.target --------------------------------------------------------------------------------