├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── devices ├── 0x10-CH56x.yaml ├── 0x11-CH55x.yaml ├── 0x12-CH54x.yaml ├── 0x13-CH57x.yaml ├── 0x14-CH32F103.yaml ├── 0x15-CH32V103.yaml ├── 0x16-CH58x.yaml ├── 0x17-CH32V30x.yaml ├── 0x18-CH32F20x.yaml ├── 0x19-CH32V20x.yaml ├── 0x20-CH32F20x-Compact.yaml ├── 0x21-CH32V00x.yaml ├── 0x22-CH59x.yaml ├── 0x23-CH32X03x.yaml ├── 0x24-CH643.yaml ├── 0x25-CH32L103.yaml └── SCHEMA.yaml └── src ├── constants.rs ├── device.rs ├── flashing.rs ├── format.rs ├── lib.rs ├── main.rs ├── protocol.rs └── transport ├── mod.rs ├── serial.rs └── usb.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | paths: 6 | - "Cargo.toml" 7 | - "src/**" 8 | - "devices/**" 9 | - "!**.md" 10 | - ".github/workflows/rust.yml" 11 | pull_request: 12 | branches: [ main ] 13 | paths: 14 | - "Cargo.toml" 15 | - "src/**" 16 | - "devices/**" 17 | - "!**.md" 18 | - ".github/workflows/rust.yml" 19 | workflow_dispatch: 20 | release: 21 | types: 22 | - created 23 | schedule: # Every day at the 2 P.M. (UTC) we run a scheduled nightly build 24 | - cron: "0 14 * * *" 25 | 26 | concurrency: 27 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 28 | cancel-in-progress: true 29 | 30 | env: 31 | CARGO_TERM_COLOR: always 32 | 33 | jobs: 34 | build: 35 | name: build (${{ matrix.config.arch }}) 36 | strategy: 37 | matrix: 38 | config: 39 | - os: windows-latest 40 | arch: win-x64 41 | - os: ubuntu-latest 42 | arch: linux-x64 43 | - os: ubuntu-latest 44 | arch: linux-aarch64 45 | - os: macos-latest 46 | arch: macos-arm64 47 | - os: macos-13 48 | arch: macos-x64 49 | runs-on: ${{ matrix.config.os }} 50 | 51 | steps: 52 | - uses: actions/checkout@v4 53 | - name: Configure target 54 | run: | 55 | if [[ "${{ matrix.config.arch }}" == "linux-aarch64" ]]; then 56 | rustup target add aarch64-unknown-linux-gnu 57 | sudo apt-get install gcc-aarch64-linux-gnu 58 | echo TARGET="--target aarch64-unknown-linux-gnu" >> $GITHUB_ENV 59 | echo RUSTFLAGS="-C linker=aarch64-linux-gnu-gcc" >> $GITHUB_ENV 60 | else 61 | echo TARGET="" >> $GITHUB_ENV 62 | fi 63 | shell: bash 64 | 65 | - name: Build 66 | run: cargo build --release ${{ env.TARGET }} 67 | 68 | - name: Run tests 69 | if: matrix.config.arch != 'linux-aarch64' 70 | run: cargo test --release ${{ env.TARGET }} 71 | 72 | - name: Run help 73 | if: matrix.config.arch != 'linux-aarch64' 74 | run: cargo run --release ${{ env.TARGET }} -- --help 75 | 76 | - name: Prepare artifacts 77 | run: | 78 | if [[ "${{ matrix.config.arch }}" == "win-x64" ]]; then 79 | WCHISP_EXE="target/release/wchisp.exe" 80 | elif [[ "${{ matrix.config.arch }}" == "linux-aarch64" ]]; then 81 | WCHISP_EXE="target/aarch64-unknown-linux-gnu/release/wchisp" 82 | else 83 | WCHISP_EXE="target/release/wchisp" 84 | fi 85 | 86 | mkdir wchisp-${{ matrix.config.arch }} 87 | cp $WCHISP_EXE wchisp-${{ matrix.config.arch }} 88 | cp README.md wchisp-${{ matrix.config.arch }} 89 | shell: bash 90 | - uses: actions/upload-artifact@v4 91 | with: 92 | name: wchisp-${{ matrix.config.arch }} 93 | path: wchisp-${{ matrix.config.arch }} 94 | 95 | - name: Prepare Release Asset 96 | if: github.event_name == 'release' 97 | run: | 98 | if [[ "${{ runner.os }}" == "Windows" ]]; then 99 | 7z a -tzip wchisp-${{ github.event.release.tag_name }}-${{ matrix.config.arch }}.zip wchisp-${{ matrix.config.arch }} 100 | else 101 | tar -czvf wchisp-${{ github.event.release.tag_name }}-${{ matrix.config.arch }}.tar.gz wchisp-${{ matrix.config.arch }} 102 | fi 103 | shell: bash 104 | - name: Upload Release Asset 105 | uses: softprops/action-gh-release@v2 106 | if: github.event_name == 'release' 107 | with: 108 | fail_on_unmatched_files: false 109 | files: | 110 | wchisp-*.tar.gz 111 | wchisp-*.zip 112 | 113 | nightly-release: 114 | needs: build 115 | runs-on: ubuntu-latest 116 | if: github.event_name == 'schedule' 117 | steps: 118 | - name: Download Artifacts 119 | uses: actions/download-artifact@v4 120 | with: 121 | path: ./ 122 | 123 | - name: Prepare Nightly Asset 124 | run: | 125 | ls -R ./ 126 | for f in wchisp-*; do 127 | echo "Compressing $f" 128 | if [[ $f == wchisp-win* ]]; then 129 | zip -r $f.zip $f 130 | else 131 | tar -czvf $f.tar.gz $f 132 | fi 133 | done 134 | ls ./ 135 | 136 | - name: Update Nightly Release 137 | uses: andelf/nightly-release@main 138 | env: 139 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 140 | with: 141 | tag_name: nightly 142 | name: "wchisp nightly release $$" 143 | draft: false 144 | prerelease: true 145 | body: | 146 | This is a nightly binary release of the wchisp command line tool. 147 | files: | 148 | wchisp-*.tar.gz 149 | wchisp-*.zip 150 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | trash/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | 12 | - Add CH32X033 support 13 | - Add CH585 support 14 | - New `enable-debug` subcommand, also added to chip metadata 15 | 16 | ## [0.2.2] - 2023-10-03 17 | 18 | ### Added 19 | 20 | - Refine device opening error message on Windows and Linux 21 | - Add CH59x support 22 | - Add CH32X03x support 23 | - Add CH643 support(not tested) 24 | - Add prebuilt binaries for Windows, Linux and macOS in the nightly release page 25 | - Serieal transport support (#56) 26 | 27 | ### Fixed 28 | 29 | - Verification error of CH58x caused by wrong CFG reset value #26 30 | - Ignore USB device handle drop errors 31 | 32 | ## [0.2.2] - 2023-02-20 33 | 34 | ### Added 35 | 36 | - Enable 2-wire debug for ch57x, update default reset config reg values 37 | 38 | ### Fixed 39 | 40 | - Hang on Linux caused by libusb timeout #22 41 | 42 | ## [0.2.1] - 2023-01-28 43 | 44 | ### Added 45 | 46 | - EEPROM erase support 47 | - EEPROM write support 48 | - Config register for CH57x 49 | 50 | ### Fixed 51 | 52 | - Enable adaptive timeout setting 53 | 54 | ## [0.2.0] - 2022-11-13 55 | 56 | ### Added 57 | 58 | - EEPROM dump support, fix #12 59 | - Refactor all subcommands, using clap v4 60 | - Probe support, multiple chips can be selected by an index 61 | - Progressbar for flash and verify commands 62 | 63 | ### Changed 64 | 65 | - Disable debug log by default 66 | 67 | ### Fixed 68 | 69 | - Wrong timeout setting for usb transport 70 | 71 | ## [0.1.4] - 2022-11-13 72 | 73 | ### Added 74 | 75 | - Config register definition for CH32F10x, CH32V20x, CH55x, CH58x 76 | - Code erase impl 77 | - Add schema for device description yaml 78 | - Add no-verify, no-reset, no-erase to flash cmd 79 | 80 | ### Fixed 81 | 82 | - Wrong verify impl 83 | - Ignore reset protocol errors 84 | - Wrong field definitions #2 #3 85 | - Wrong chip info of CH55x 86 | 87 | ## [0.1.3] - 2022-05-14 88 | 89 | ### Added 90 | 91 | - Basic config register parsing 92 | - Config register reset support (buggy for old chips) 93 | 94 | ### Changed 95 | 96 | - Refine chip family naming 97 | 98 | ## [0.1.2] - 2022-05-11 99 | 100 | ### Added 101 | 102 | - New style chip DB, now parses MCU variants more accurately 103 | - dump `info` support 104 | 105 | ### Changed 106 | 107 | - BTVER, UID formating 108 | 109 | ### Fxied 110 | 111 | - Wrong ELF parsing 112 | - Check response code for `verify` 113 | 114 | ## [0.1.1] - 2022-05-09 115 | 116 | ### Added 117 | 118 | - flash support 119 | - ELF parsing 120 | 121 | ### Changed 122 | 123 | - Debug print format 124 | 125 | ## [0.1.0] - 2022-05-08 126 | 127 | ### Added 128 | 129 | - Initialize project - first release 130 | -------------------------------------------------------------------------------- /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.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.7" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.4" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.0.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.3" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "anyhow" 56 | version = "1.0.86" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 59 | 60 | [[package]] 61 | name = "bitfield" 62 | version = "0.17.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" 65 | 66 | [[package]] 67 | name = "bitflags" 68 | version = "1.3.2" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 71 | 72 | [[package]] 73 | name = "bitflags" 74 | version = "2.6.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 77 | 78 | [[package]] 79 | name = "cc" 80 | version = "1.0.98" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" 83 | 84 | [[package]] 85 | name = "cfg-if" 86 | version = "1.0.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 89 | 90 | [[package]] 91 | name = "clap" 92 | version = "4.5.4" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 95 | dependencies = [ 96 | "clap_builder", 97 | "clap_derive", 98 | ] 99 | 100 | [[package]] 101 | name = "clap_builder" 102 | version = "4.5.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 105 | dependencies = [ 106 | "anstream", 107 | "anstyle", 108 | "clap_lex", 109 | "strsim", 110 | ] 111 | 112 | [[package]] 113 | name = "clap_derive" 114 | version = "4.5.4" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 117 | dependencies = [ 118 | "heck", 119 | "proc-macro2", 120 | "quote", 121 | "syn", 122 | ] 123 | 124 | [[package]] 125 | name = "clap_lex" 126 | version = "0.7.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 129 | 130 | [[package]] 131 | name = "colorchoice" 132 | version = "1.0.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 135 | 136 | [[package]] 137 | name = "console" 138 | version = "0.15.8" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 141 | dependencies = [ 142 | "encode_unicode", 143 | "lazy_static", 144 | "libc", 145 | "unicode-width", 146 | "windows-sys", 147 | ] 148 | 149 | [[package]] 150 | name = "core-foundation-sys" 151 | version = "0.8.7" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 154 | 155 | [[package]] 156 | name = "deranged" 157 | version = "0.3.11" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 160 | dependencies = [ 161 | "powerfmt", 162 | ] 163 | 164 | [[package]] 165 | name = "encode_unicode" 166 | version = "0.3.6" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 169 | 170 | [[package]] 171 | name = "equivalent" 172 | version = "1.0.1" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 175 | 176 | [[package]] 177 | name = "getrandom" 178 | version = "0.2.15" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 181 | dependencies = [ 182 | "cfg-if", 183 | "libc", 184 | "wasi", 185 | ] 186 | 187 | [[package]] 188 | name = "hashbrown" 189 | version = "0.14.5" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 192 | 193 | [[package]] 194 | name = "heck" 195 | version = "0.5.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 198 | 199 | [[package]] 200 | name = "hex" 201 | version = "0.4.3" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 204 | 205 | [[package]] 206 | name = "hxdmp" 207 | version = "0.2.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "a17b27f28a7466846baca75f0a5244e546e44178eb7f1c07a3820f413e91c6b0" 210 | 211 | [[package]] 212 | name = "ihex" 213 | version = "3.0.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" 216 | 217 | [[package]] 218 | name = "indexmap" 219 | version = "2.2.6" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 222 | dependencies = [ 223 | "equivalent", 224 | "hashbrown", 225 | ] 226 | 227 | [[package]] 228 | name = "indicatif" 229 | version = "0.17.8" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 232 | dependencies = [ 233 | "console", 234 | "instant", 235 | "number_prefix", 236 | "portable-atomic", 237 | "unicode-width", 238 | ] 239 | 240 | [[package]] 241 | name = "instant" 242 | version = "0.1.13" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 245 | dependencies = [ 246 | "cfg-if", 247 | ] 248 | 249 | [[package]] 250 | name = "io-kit-sys" 251 | version = "0.4.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" 254 | dependencies = [ 255 | "core-foundation-sys", 256 | "mach2", 257 | ] 258 | 259 | [[package]] 260 | name = "is_terminal_polyfill" 261 | version = "1.70.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 264 | 265 | [[package]] 266 | name = "itoa" 267 | version = "1.0.11" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 270 | 271 | [[package]] 272 | name = "lazy_static" 273 | version = "1.4.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 276 | 277 | [[package]] 278 | name = "libc" 279 | version = "0.2.155" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 282 | 283 | [[package]] 284 | name = "libusb1-sys" 285 | version = "0.7.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" 288 | dependencies = [ 289 | "cc", 290 | "libc", 291 | "pkg-config", 292 | "vcpkg", 293 | ] 294 | 295 | [[package]] 296 | name = "log" 297 | version = "0.4.21" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 300 | 301 | [[package]] 302 | name = "mach2" 303 | version = "0.4.2" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" 306 | dependencies = [ 307 | "libc", 308 | ] 309 | 310 | [[package]] 311 | name = "memchr" 312 | version = "2.7.2" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 315 | 316 | [[package]] 317 | name = "nix" 318 | version = "0.26.4" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 321 | dependencies = [ 322 | "bitflags 1.3.2", 323 | "cfg-if", 324 | "libc", 325 | ] 326 | 327 | [[package]] 328 | name = "num-conv" 329 | version = "0.1.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 332 | 333 | [[package]] 334 | name = "num_threads" 335 | version = "0.1.7" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 338 | dependencies = [ 339 | "libc", 340 | ] 341 | 342 | [[package]] 343 | name = "number_prefix" 344 | version = "0.4.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 347 | 348 | [[package]] 349 | name = "object" 350 | version = "0.36.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" 353 | dependencies = [ 354 | "memchr", 355 | ] 356 | 357 | [[package]] 358 | name = "pkg-config" 359 | version = "0.3.30" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 362 | 363 | [[package]] 364 | name = "portable-atomic" 365 | version = "1.6.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 368 | 369 | [[package]] 370 | name = "powerfmt" 371 | version = "0.2.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 374 | 375 | [[package]] 376 | name = "ppv-lite86" 377 | version = "0.2.17" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 380 | 381 | [[package]] 382 | name = "proc-macro2" 383 | version = "1.0.83" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" 386 | dependencies = [ 387 | "unicode-ident", 388 | ] 389 | 390 | [[package]] 391 | name = "quote" 392 | version = "1.0.36" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 395 | dependencies = [ 396 | "proc-macro2", 397 | ] 398 | 399 | [[package]] 400 | name = "rand" 401 | version = "0.8.5" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 404 | dependencies = [ 405 | "libc", 406 | "rand_chacha", 407 | "rand_core", 408 | ] 409 | 410 | [[package]] 411 | name = "rand_chacha" 412 | version = "0.3.1" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 415 | dependencies = [ 416 | "ppv-lite86", 417 | "rand_core", 418 | ] 419 | 420 | [[package]] 421 | name = "rand_core" 422 | version = "0.6.4" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 425 | dependencies = [ 426 | "getrandom", 427 | ] 428 | 429 | [[package]] 430 | name = "rusb" 431 | version = "0.9.4" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" 434 | dependencies = [ 435 | "libc", 436 | "libusb1-sys", 437 | ] 438 | 439 | [[package]] 440 | name = "ryu" 441 | version = "1.0.18" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 444 | 445 | [[package]] 446 | name = "scopeguard" 447 | version = "1.2.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 450 | 451 | [[package]] 452 | name = "scroll" 453 | version = "0.12.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 456 | 457 | [[package]] 458 | name = "serde" 459 | version = "1.0.202" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" 462 | dependencies = [ 463 | "serde_derive", 464 | ] 465 | 466 | [[package]] 467 | name = "serde_derive" 468 | version = "1.0.202" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" 471 | dependencies = [ 472 | "proc-macro2", 473 | "quote", 474 | "syn", 475 | ] 476 | 477 | [[package]] 478 | name = "serde_yaml" 479 | version = "0.9.34+deprecated" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 482 | dependencies = [ 483 | "indexmap", 484 | "itoa", 485 | "ryu", 486 | "serde", 487 | "unsafe-libyaml", 488 | ] 489 | 490 | [[package]] 491 | name = "serialport" 492 | version = "4.5.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "241ebb629ed9bf598b2b392ba42aa429f9ef2a0099001246a36ac4c084ee183f" 495 | dependencies = [ 496 | "bitflags 2.6.0", 497 | "cfg-if", 498 | "core-foundation-sys", 499 | "io-kit-sys", 500 | "mach2", 501 | "nix", 502 | "scopeguard", 503 | "unescaper", 504 | "winapi", 505 | ] 506 | 507 | [[package]] 508 | name = "simplelog" 509 | version = "0.12.2" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" 512 | dependencies = [ 513 | "log", 514 | "termcolor", 515 | "time", 516 | ] 517 | 518 | [[package]] 519 | name = "strsim" 520 | version = "0.11.1" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 523 | 524 | [[package]] 525 | name = "syn" 526 | version = "2.0.65" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" 529 | dependencies = [ 530 | "proc-macro2", 531 | "quote", 532 | "unicode-ident", 533 | ] 534 | 535 | [[package]] 536 | name = "termcolor" 537 | version = "1.4.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 540 | dependencies = [ 541 | "winapi-util", 542 | ] 543 | 544 | [[package]] 545 | name = "thiserror" 546 | version = "1.0.63" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 549 | dependencies = [ 550 | "thiserror-impl", 551 | ] 552 | 553 | [[package]] 554 | name = "thiserror-impl" 555 | version = "1.0.63" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 558 | dependencies = [ 559 | "proc-macro2", 560 | "quote", 561 | "syn", 562 | ] 563 | 564 | [[package]] 565 | name = "time" 566 | version = "0.3.36" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 569 | dependencies = [ 570 | "deranged", 571 | "itoa", 572 | "libc", 573 | "num-conv", 574 | "num_threads", 575 | "powerfmt", 576 | "serde", 577 | "time-core", 578 | "time-macros", 579 | ] 580 | 581 | [[package]] 582 | name = "time-core" 583 | version = "0.1.2" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 586 | 587 | [[package]] 588 | name = "time-macros" 589 | version = "0.2.18" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 592 | dependencies = [ 593 | "num-conv", 594 | "time-core", 595 | ] 596 | 597 | [[package]] 598 | name = "unescaper" 599 | version = "0.1.5" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" 602 | dependencies = [ 603 | "thiserror", 604 | ] 605 | 606 | [[package]] 607 | name = "unicode-ident" 608 | version = "1.0.12" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 611 | 612 | [[package]] 613 | name = "unicode-width" 614 | version = "0.1.12" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" 617 | 618 | [[package]] 619 | name = "unsafe-libyaml" 620 | version = "0.2.11" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 623 | 624 | [[package]] 625 | name = "utf8parse" 626 | version = "0.2.1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 629 | 630 | [[package]] 631 | name = "vcpkg" 632 | version = "0.2.15" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 635 | 636 | [[package]] 637 | name = "wasi" 638 | version = "0.11.0+wasi-snapshot-preview1" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 641 | 642 | [[package]] 643 | name = "wchisp" 644 | version = "0.3.0" 645 | dependencies = [ 646 | "anyhow", 647 | "bitfield", 648 | "clap", 649 | "hex", 650 | "hxdmp", 651 | "ihex", 652 | "indicatif", 653 | "log", 654 | "object", 655 | "rand", 656 | "rusb", 657 | "scroll", 658 | "serde", 659 | "serde_yaml", 660 | "serialport", 661 | "simplelog", 662 | ] 663 | 664 | [[package]] 665 | name = "winapi" 666 | version = "0.3.9" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 669 | dependencies = [ 670 | "winapi-i686-pc-windows-gnu", 671 | "winapi-x86_64-pc-windows-gnu", 672 | ] 673 | 674 | [[package]] 675 | name = "winapi-i686-pc-windows-gnu" 676 | version = "0.4.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 679 | 680 | [[package]] 681 | name = "winapi-util" 682 | version = "0.1.8" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 685 | dependencies = [ 686 | "windows-sys", 687 | ] 688 | 689 | [[package]] 690 | name = "winapi-x86_64-pc-windows-gnu" 691 | version = "0.4.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 694 | 695 | [[package]] 696 | name = "windows-sys" 697 | version = "0.52.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 700 | dependencies = [ 701 | "windows-targets", 702 | ] 703 | 704 | [[package]] 705 | name = "windows-targets" 706 | version = "0.52.5" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 709 | dependencies = [ 710 | "windows_aarch64_gnullvm", 711 | "windows_aarch64_msvc", 712 | "windows_i686_gnu", 713 | "windows_i686_gnullvm", 714 | "windows_i686_msvc", 715 | "windows_x86_64_gnu", 716 | "windows_x86_64_gnullvm", 717 | "windows_x86_64_msvc", 718 | ] 719 | 720 | [[package]] 721 | name = "windows_aarch64_gnullvm" 722 | version = "0.52.5" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 725 | 726 | [[package]] 727 | name = "windows_aarch64_msvc" 728 | version = "0.52.5" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 731 | 732 | [[package]] 733 | name = "windows_i686_gnu" 734 | version = "0.52.5" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 737 | 738 | [[package]] 739 | name = "windows_i686_gnullvm" 740 | version = "0.52.5" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 743 | 744 | [[package]] 745 | name = "windows_i686_msvc" 746 | version = "0.52.5" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 749 | 750 | [[package]] 751 | name = "windows_x86_64_gnu" 752 | version = "0.52.5" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 755 | 756 | [[package]] 757 | name = "windows_x86_64_gnullvm" 758 | version = "0.52.5" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 761 | 762 | [[package]] 763 | name = "windows_x86_64_msvc" 764 | version = "0.52.5" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 767 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wchisp" 3 | version = "0.3.0" 4 | edition = "2021" 5 | authors = ["Andelf "] 6 | repository = "https://github.com/ch32-rs/wchisp" 7 | documentation = "https://docs.rs/wchisp/" 8 | homepage = "https://github.com/ch32-rs/wchisp" 9 | categories = ["embedded", "command-line-utilities", "command-line-utilities"] 10 | description = "A command-line implementation of WCHISPTool, for flashing ch32 MCUs" 11 | keywords = ["wch", "cli", "ch32", "embedded"] 12 | readme = "README.md" 13 | license = "GPL-2.0" 14 | include = ["**/*.rs", "devices/**/*.yaml", "Cargo.toml", "LICENSE", "README.md"] 15 | 16 | [features] 17 | default = ["vendored-libusb"] 18 | 19 | vendored-libusb = ["rusb/vendored"] 20 | 21 | [dependencies] 22 | rand = "0.8" 23 | log = "0.4" 24 | serde = { version = "1.0", features = ["derive"] } 25 | serde_yaml = "0.9" 26 | clap = { version = "4", features = ["derive"] } 27 | anyhow = "1.0" 28 | rusb = { version = "0.9.2" } 29 | bitfield = "0.17.0" 30 | scroll = "0.12.0" 31 | simplelog = "0.12" 32 | hex = "0.4" 33 | ihex = "3" 34 | hxdmp = "0.2" 35 | object = { version = "0.36.0", default-features = false, features = [ 36 | "elf", 37 | "read_core", 38 | "std", 39 | ] } 40 | indicatif = "0.17" 41 | serialport = { version = "4.5", default-features = false } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wchisp - WCH ISP Tool in Rust 2 | 3 | ![crates.io](https://img.shields.io/crates/v/wchisp.svg) 4 | 5 | Command-line implementation of the WCHISPTool in Rust, by the ch32-rs team. 6 | 7 | This tool is a work in progress. 8 | 9 | > [!NOTE] 10 | > This tool is for **USB** and **UART** ISP, not for use with WCH-Link. 11 | > - [ch32-rs/wlink](https://github.com/ch32-rs/wlink) is a command line tool for WCH-Link. 12 | 13 | ## Installing 14 | 15 | The prebuilt binaries are available on the [Nightly release page](https://github.com/ch32-rs/wchisp/releases/tag/nightly). 16 | 17 | For Windows users, you will need VC runtime to run the binary. You can download it from [Microsoft](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170). 18 | 19 | Or else, you can install it from source. 20 | 21 | ```console 22 | # install libusb for your platform 23 | # macOS 24 | > brew install libusb 25 | # Ubuntu 26 | > sudo apt install libusb-1.0-0-dev 27 | 28 | # install wchisp 29 | > cargo install wchisp --git https://github.com/ch32-rs/wchisp 30 | # or use 31 | > cargo install wchisp --force 32 | ``` 33 | 34 | ### Prebuilt Binaries 35 | 36 | Prebuilt binaries are available on the Github Actions Page. 37 | Click the newest runs at [Github Actions Page](https://github.com/ch32-rs/wchisp/actions/workflows/rust.yml) and download the binary from "Artifacts" section. 38 | 39 | ### Note for Windows 40 | 41 | If you are using Windows, you need to install the WinUSB driver for your device. 42 | See [Zadig](https://zadig.akeo.ie/). 43 | 44 | NOTE: This is not compatible with the Official WCH driver you installed with IDE. 45 | 46 | ### Note for Linux 47 | 48 | If you are using Linux, you need to set the udev rules for your device. 49 | 50 | ```text 51 | # /etc/udev/rules.d/50-wchisp.rules 52 | SUBSYSTEM=="usb", ATTRS{idVendor}=="4348", ATTRS{idProduct}=="55e0", MODE="0666" 53 | # or replace MODE="0666" with GROUP="plugdev" or something else 54 | ``` 55 | 56 | ### Arch Linux 57 | 58 | Arch Linux users can install [wchisp](https://aur.archlinux.org/packages/wchisp) or [wchisp-git](https://aur.archlinux.org/packages/wchisp-git) via the AUR. 59 | 60 | ```bash 61 | yay -S wchisp 62 | ``` 63 | 64 | or 65 | 66 | ```bash 67 | yay -S wchisp-git 68 | ``` 69 | 70 | ## Usage 71 | 72 | ```console 73 | > wchisp info 74 | 14:51:24 [INFO] Chip: CH32V307VCT6[0x7017] (Code Flash: 256KiB) 75 | 14:51:24 [INFO] Chip UID: 30-78-3e-26-3b-38-a9-d6 76 | 14:51:24 [INFO] BTVER(bootloader ver): 02.60 77 | 14:51:24 [INFO] Code Flash protected: false 78 | RDPR_USER: 0x9F605AA5 79 | [7:0] RDPR 0b10100101 (0xA5) 80 | `- Unprotected 81 | [16:16] IWDG_SW 0b0 (0x0) 82 | `- IWDG enabled by the software 83 | [17:17] STOP_RST 0b0 (0x0) 84 | `- Enable 85 | [18:18] STANDBY_RST 0b0 (0x0) 86 | `- Enable 87 | [23:21] SRAM_CODE_MODE 0b11 (0x3) 88 | `- CODE-228KB + RAM-32KB 89 | DATA: 0x00FF00FF 90 | [7:0] DATA0 0b11111111 (0xFF) 91 | [23:16] DATA1 0b11111111 (0xFF) 92 | WRP: 0xFFFFFFFF 93 | `- Unprotected 94 | 95 | > wchisp flash ./path/to/firmware.{bin,hex,elf} 96 | 97 | > wchisp config info 98 | 99 | > wchisp config reset 100 | ``` 101 | 102 | ### CH32V00x Notes 103 | 104 | The CH32V00x series **DOES NOT** have a USB ISP interface; it can only be accessed via UART. Use `-s` or `--serial` command-line option to specify serial transport, and `-p` or `--port` option to specify COM/TTY port. 105 | 106 | Also note that ISP bootloader entry cannot be controlled via external pin state at reset. Instead, user application code must instruct device to enter the bootloader via setting `FLASH_STATR.MODE` flag and performing a software reset (see `PFIC_CFGR`). 107 | 108 | ## Tested On 109 | 110 | This tool should work on most WCH MCU chips. But I haven't tested it on any other chips. 111 | 112 | - [x] CH32V307 113 | - VCT6 114 | - RCT6 #8 115 | - [x] CH32V103 116 | - [x] CH32F103 117 | - [x] CH549 118 | - [VeriMake CH549DB CH549F](https://gitee.com/verimaker/vm-wch-51-evb-ch549evb/blob/master/doc/Schematic_VeriMake_CH549DB_v1_0_0_20220123.pdf) 119 | - [x] CH552 120 | - Works but might be buggy #10 #14 121 | - [x] CH582 122 | - CH58xM-EVT 123 | - [FOSSASIA Badgemagic CH582M](https://badgemagic.fossasia.org/) 124 | - [WeAct Studio CH582F](https://github.com/WeActStudio/WeActStudio.WCH-BLE-Core/blob/master/Images/1.png) 125 | - [x] CH573 126 | - [WeAct Studio CH573F](https://github.com/WeActStudio/WeActStudio.WCH-BLE-Core/blob/master/Images/1.png) 127 | - [x] CH592 128 | - [WeAct Studio CH592F](https://github.com/WeActStudio/WeActStudio.WCH-BLE-Core/blob/master/Images/2.png) 129 | - [x] CH579 130 | - BTVER: 02.90 #18 131 | - [x] CH559 132 | - CH559TL_MINIEVT_V20 by wch.cn 133 | - [x] CH32V203 134 | - [CH32V203G6 FlappyBoard](https://github.com/metro94/FlappyBoard) 135 | - [nanoCH32V203](https://github.com/wuxx/nanoCH32V203) 136 | - [x] CH32V003 137 | - [x] CH32X035 138 | - ... (feel free to open an issue whether it works on your chip or not) 139 | 140 | ## TODOs 141 | 142 | - [x] chip detection, identification 143 | - `wchisp probe` 144 | - `wchisp info` 145 | - [x] flash and verify code 146 | - [x] ELF parsing 147 | - [x] hex, bin, ihex support 148 | - [x] skip erasing, verifying, resetting 149 | - [x] chip config register dump 150 | - `wchisp config` 151 | - works for most chips, but not all. Issues and PRs are welcomed 152 | - [ ] write config registers 153 | - [x] reset config registers to default 154 | - [ ] write config with friendly register names? like `wchisp config set SRAM_CODE_MODE=1 ...` 155 | - [x] EEPROM dump 156 | - [x] EEPROM erase 157 | - [x] EEPROM write 158 | - [x] select from multiple chips (using `-d` to select device index) `wchisp -d 0 info` 159 | - [x] ISP via UART 160 | - [ ] ISP via Net 161 | 162 | ## Related Works (Many Thanks!) 163 | 164 | - 165 | - by [@Pe3ucTop](https://github.com/Pe3ucTop/ch552tool/tree/global_rework) 166 | - 167 | - 168 | - 169 | - 170 | 171 | ### Contribution 172 | 173 | This project is under active development. If you have any suggestions or bug reports, please open an issue. 174 | 175 | If it works for your devices, please open a pull request to modify this README page. 176 | 177 | It it doesn't, please open an issue. Better provide the following information: 178 | 179 | - Chip type (with variant suffix). 180 | - Debug print of USB packets. 181 | - Correct USB packets to negotiate with the chip (via USBPcap or other tools). 182 | -------------------------------------------------------------------------------- /devices/0x10-CH56x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH56x Series 3 | mcu_type: 0 4 | device_type: 0x10 5 | support_serial: true 6 | description: CH56x Series, RISC-V3A (CH569/CH565), ARM9 like (CH563/CH561), RISC (CH566/CH567/CH568) NDS32? (CH568) 7 | # Section 2.2.1 On-chip non-volatile memory map 8 | config_registers_ch569: &config_registers_ch_565_ch569 9 | - offset: 0x00 10 | name: UNKNOWN0 11 | description: Reserved 32-bit word 12 | reset: 0xFFFFFFFF 13 | type: u32 14 | - offset: 0x04 15 | name: UNKNOWN1 16 | description: Reserved 32-bit word 17 | reset: 0xFFFFFFFF 18 | type: u32 19 | - offset: 0x08 20 | name: NV_INFO 21 | description: Non-volatile information 22 | reset: 0x8FFFF2E5 23 | type: u32 24 | fields: 25 | - bit_range: [31,30] 26 | name: USER_MEM 27 | description: System RAMX/ROM capacity redefine configuration. 28 | explaination: 29 | 0b00: RAMX 32KB + ROM 96KB 30 | 0b01: RAMX 64KB + ROM 64KB 31 | _: RAMX 96KB + ROM 32KB 32 | - bit_range: [29,29] 33 | name: LOCKUP_RST_EN 34 | description: Core LOCKUP reset system enable 35 | explaination: 36 | 1: Reset 37 | 0: NotReset 38 | - bit_range: [28,28] 39 | name: RESERVED1 40 | explaination: 41 | 0: Reserved 42 | _: Error 43 | - bit_range: [27,12] 44 | name: RESERVED2 45 | explaination: 46 | 0xffff: Reserved 47 | _: Error 48 | - bit_range: [11,10] 49 | name: RESERVED3 50 | explaination: 51 | 0b00: Reserved 52 | _: Error 53 | - bit_range: [9,8] 54 | name: RESERVED4 55 | explaination: 56 | 0b10: Reserved 57 | _: Error 58 | - bit_range: [7,7] 59 | name: CODE_READ_EN 60 | description: External programmer read FLASH enable 61 | explaination: 62 | 1: Enable 63 | 0: Disable 64 | - bit_range: [6,6] 65 | name: BOOT_EN 66 | description: Bootloader function enable 67 | explaination: 68 | 1: Enable 69 | 0: Disable 70 | - bit_range: [5,5] 71 | name: DEBUG_EN 72 | description: Debug interface enable 73 | explaination: 74 | 1: Enable 75 | 0: Disable 76 | - bit_range: [4,4] 77 | name: RESET_EN 78 | description: External reset enable 79 | explaination: 80 | 1: Enable via PB15 81 | 0: Disable, PB15 is used as GPIO 82 | - bit_range: [3,0] 83 | name: RESERVED5 84 | explaination: 85 | 0b0101: Reserved 86 | _: Error 87 | variants: 88 | # use 0x46 to probe 89 | - name: CH561 90 | chip_id: 0x61 91 | alt_chip_ids: [0x46] 92 | flash_size: 64K 93 | eeprom_size: 28K 94 | support_usb: false 95 | support_net: true 96 | 97 | # use 0x42 to probe 98 | - name: CH563 99 | chip_id: 0x63 100 | # 0x45 for CH563_E? 101 | alt_chip_ids: [0x42, 0x43, 0x44, 0x45] 102 | flash_size: 224K 103 | eeprom_size: 28K 104 | support_usb: true 105 | support_net: true 106 | 107 | - name: CH565 108 | chip_id: 0x65 109 | flash_size: 448K 110 | eeprom_size: 32K 111 | support_usb: true 112 | support_net: false 113 | eeprom_start_addr: 0 114 | config_registers: *config_registers_ch_565_ch569 115 | 116 | - name: CH566 117 | chip_id: 0x66 118 | flash_size: 64K 119 | eeprom_size: 32K 120 | support_usb: true 121 | support_net: false 122 | eeprom_start_addr: 0 123 | 124 | - name: CH567 125 | chip_id: 0x67 126 | flash_size: 192K 127 | eeprom_size: 32K 128 | support_usb: true 129 | support_net: false 130 | 131 | - name: CH568 132 | chip_id: 0x68 133 | flash_size: 192K 134 | eeprom_size: 32K 135 | support_usb: true 136 | support_net: false 137 | 138 | - name: CH569 139 | chip_id: 0x69 140 | flash_size: 448K 141 | eeprom_size: 32K 142 | support_usb: true 143 | support_net: false 144 | config_registers: *config_registers_ch_565_ch569 145 | -------------------------------------------------------------------------------- /devices/0x11-CH55x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH55x Series 3 | mcu_type: 1 4 | device_type: 0x11 5 | support_usb: true 6 | support_serial: true 7 | support_net: false 8 | description: CH55x (E8051) Series 9 | config_registers: 10 | - offset: 0x00 11 | name: REVERSED 12 | description: Reversed 32-bit word 13 | reset: 0xFFFFFFFF 14 | type: u32 15 | - offset: 0x04 16 | name: WPROTECT 17 | reset: 0xFFFFFFFF 18 | type: u32 19 | fields: 20 | - bit_range: [0, 0] 21 | name: NO_KEY_SERIAL_DOWNLOAD 22 | description: Turn on No-key serial port download 23 | explaination: 24 | 1: Enable 25 | 0: Disable 26 | - bit_range: [1, 1] 27 | name: DOWNLOAD_CFG 28 | explaination: 29 | 1: P4.6 / P15 / P3.6(Default set) 30 | 0: P5.1 / P51 / P1.5 31 | - offset: 0x08 32 | name: GLOBAL_CFG 33 | reset: 0xFFFF4EFF 34 | type: u32 35 | fields: 36 | # Configuration Information, sec 6.2 37 | - bit_range: [15, 15] 38 | name: CODE_PROTECT 39 | explaination: 40 | 0: Forbid code & data protection 41 | 1: Readable 42 | - bit_range: [14, 14] 43 | name: NO_BOOT_LOAD 44 | explaination: 45 | 0: Boot from 0x0000 Application 46 | 1: Boot from 0xf400 Bootloader 47 | - bit_range: [13, 13] 48 | name: EN_LONG_RESET 49 | explaination: 50 | 0: Short reset 51 | 1: Wide reset, add 87ms reset time 52 | - bit_range: [12, 12] 53 | name: XT_OSC_STRONG 54 | explaination: 55 | 0: Standard 56 | 1: Enhanced 57 | - bit_range: [11, 11] 58 | name: EN_P5.7_RESET 59 | explaination: 60 | 0: Forbid 61 | 1: Enable reset 62 | - bit_range: [10, 10] 63 | name: EN_P0_PULLUP 64 | explaination: 65 | 0: Forbid 66 | 1: Enable 67 | - bit_range: [9, 8] 68 | name: RESERVED 69 | explaination: 70 | 0b10: Default 71 | _: Error 72 | - bit_range: [7, 0] 73 | name: RESERVED 74 | explaination: 75 | 0b11111111: Default 76 | _: Error 77 | variants: 78 | - name: CH551 79 | chip_id: 0x51 80 | flash_size: 10K 81 | eeprom_size: 128 82 | eeprom_start_addr: 0xC000 83 | 84 | - name: CH552 85 | chip_id: 0x52 86 | # FIXME: 16K or 14K 87 | flash_size: 14K 88 | eeprom_size: 128 89 | eeprom_start_addr: 0xC000 90 | 91 | - name: CH554 92 | chip_id: 0x54 93 | flash_size: 14K 94 | eeprom_size: 128 95 | eeprom_start_addr: 0xC000 96 | 97 | - name: CH555 98 | chip_id: 0x55 99 | flash_size: 61440 100 | eeprom_size: 1K 101 | eeprom_start_addr: 61440 102 | 103 | - name: CH556 104 | chip_id: 0x56 105 | flash_size: 61440 106 | eeprom_size: 1K 107 | eeprom_start_addr: 61440 108 | 109 | - name: CH557 110 | chip_id: 0x57 111 | flash_size: 61440 112 | eeprom_size: 1024 113 | eeprom_start_addr: 61440 114 | 115 | - name: CH558 116 | chip_id: 0x58 117 | flash_size: 32768 118 | eeprom_size: 5120 119 | eeprom_start_addr: 0xE000 120 | 121 | - name: CH559 122 | chip_id: 0x59 123 | flash_size: 61440 124 | eeprom_size: 1024 125 | eeprom_start_addr: 61440 126 | -------------------------------------------------------------------------------- /devices/0x12-CH54x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH54x Series 3 | mcu_type: 2 4 | device_type: 0x12 5 | support_usb: true 6 | support_serial: true 7 | support_net: false 8 | # CH540-CH542 is not supported yet 9 | description: CH54x (E8051) Series 10 | config_registers: &config_registers_ch548_ch549 11 | - offset: 0x00 12 | name: REG0 13 | reset: 0x00000000 14 | - offset: 0x04 15 | name: REG1 16 | reset: 0x00000000 17 | fields: 18 | - bit_range: [7, 0] 19 | name: REG1_0 20 | # section 6.2 21 | - offset: 0x08 22 | name: REG2 23 | reset: 0x0000D200 24 | fields: 25 | - bit_range: [2, 0] 26 | name: LV_RST_VOL 27 | explaination: 28 | 0b000: 2.4V 29 | 0b001: 2.4V 30 | 0b010: 2.7V 31 | 0b011: 3.0V 32 | 0b100: 3.6V 33 | 0b101: 4.0V 34 | 0b110: 4.3V 35 | 0b111: 4.6V 36 | - bit_range: [8, 8] 37 | name: MUST_0 38 | - bit_range: [9, 9] 39 | name: MUST_1 40 | - bit_range: [12, 12] 41 | name: En_P5.7_RESET 42 | - bit_range: [13, 13] 43 | name: En_Long_Reset 44 | - bit_range: [14, 14] 45 | name: No_Boot_Load 46 | - bit_range: [15, 15] 47 | name: Code_Protect 48 | variants: 49 | - name: CH543 50 | chip_id: 67 51 | flash_size: 14336 52 | eeprom_size: 1024 53 | eeprom_start_addr: 14336 54 | support_serial: false 55 | 56 | - name: CH544 57 | flash_size: 61440 58 | eeprom_size: 1024 59 | chip_id: 68 60 | eeprom_start_addr: 61440 61 | 62 | - name: CH545 63 | flash_size: 61440 64 | eeprom_size: 1024 65 | chip_id: 69 66 | eeprom_start_addr: 61440 67 | 68 | - name: CH546 69 | flash_size: 32768 70 | eeprom_size: 1024 71 | chip_id: 70 72 | eeprom_start_addr: 61440 73 | 74 | - name: CH547 75 | flash_size: 61440 76 | eeprom_size: 1024 77 | chip_id: 71 78 | eeprom_start_addr: 61440 79 | 80 | - name: CH548 81 | flash_size: 32768 82 | eeprom_size: 1024 83 | chip_id: 72 84 | eeprom_start_addr: 61440 85 | config_registers: *config_registers_ch548_ch549 86 | 87 | - name: CH549 88 | flash_size: 61440 89 | eeprom_size: 1024 90 | chip_id: 73 91 | eeprom_start_addr: 61440 92 | config_registers: *config_registers_ch548_ch549 93 | -------------------------------------------------------------------------------- /devices/0x13-CH57x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH57x Series 3 | mcu_type: 3 4 | device_type: 0x13 5 | support_usb: true 6 | support_serial: true 7 | support_net: false 8 | # CH571/CH573: RISC-V3A 9 | # Others: Cortext-M0 10 | description: CH57x (Cortex-M0/RISC-V3A BLE 4.2) Series 11 | # The following applies to CH573. It seems the same as CH58x. 12 | config_registers_ch571_ch573: &config_registers_ch571_ch573 13 | - offset: 0x00 14 | name: RESERVED 15 | description: Reserved 32-bit word 16 | reset: 0xFFFFFFFF 17 | type: u32 18 | - offset: 0x04 19 | name: WPROTECT 20 | reset: 0xFFFFFFFF 21 | type: u32 22 | fields: 23 | - bit_range: [0, 0] 24 | name: NO_KEY_SERIAL_DOWNLOAD 25 | description: Turn on No-key serial port download 26 | explaination: 27 | 1: Enable 28 | 0: Disable 29 | - bit_range: [1, 1] 30 | name: DOWNLOAD_CFG 31 | explaination: 32 | 1: PB22(Default set) 33 | 0: PB11 34 | # Table 2.3 in Chip Datasheet 35 | - offset: 0x08 36 | name: USER_CFG 37 | description: User config register 38 | # reset: 0x4FFF0F4D 39 | # CFG_DEBUG_EN=1、CFG_RESET_EN=0、CFG_ROM_READ=0 40 | # enable 2-wire debug 41 | reset: 0x4FFF0F55 42 | type: u32 43 | fields: 44 | - bit_range: [2, 0] 45 | name: RESERVED 46 | explaination: 47 | 0b101: Default 48 | _: Error 49 | - bit_range: [3, 3] 50 | name: CFG_RESET_EN 51 | description: "RST# external manual reset input pin enable" 52 | explaination: 53 | 0: Disable 54 | 1: Enable 55 | - bit_range: [4, 4] 56 | name: CFG_DEBUG_EN 57 | description: "Two-wire simulation debug interface SWD enable" 58 | explaination: 59 | 0: Disable 60 | 1: Enable 61 | - bit_range: [5, 5] 62 | name: RESERVED 63 | explaination: 64 | 0: Default 65 | _: Error 66 | - bit_range: [6, 6] 67 | name: CFG_BOOT_EN 68 | description: "Bootloader enable" 69 | explaination: 70 | 0: Disable 71 | 1: Enable 72 | - bit_range: [7, 7] 73 | name: CFG_ROM_READ 74 | description: "Code and data protection mode in FlashROM" 75 | explaination: 76 | 0: Disable the programmer to read out, and keep the program secret 77 | 1: Read enable 78 | - bit_range: [27, 8] 79 | name: RESERVED 80 | explaination: 81 | 0xFFF0F: Default 82 | _: Error 83 | - bit_range: [31, 28] 84 | name: VALID_SIG 85 | description: "Configuration information valid flag, fixed value" 86 | explaination: 87 | 0b0100: Valid 88 | _: Error 89 | 90 | config_registers_ch579: &config_registers_ch579 91 | - offset: 0x00 92 | name: RESERVED 93 | description: Reserved 32-bit word 94 | reset: 0xFFFFFFFF 95 | type: u32 96 | - offset: 0x04 97 | name: WPROTECT 98 | reset: 0xFFFFFFFF 99 | type: u32 100 | fields: 101 | - bit_range: [0, 0] 102 | name: NO_KEY_SERIAL_DOWNLOAD 103 | description: Turn on No-key serial port download 104 | explaination: 105 | 1: Enable 106 | 0: Disable 107 | - bit_range: [1, 1] 108 | name: DOWNLOAD_CFG 109 | explaination: 110 | 1: PB22(Default set) 111 | 0: PB11 112 | - offset: 0x08 113 | name: USER_CFG 114 | description: User config register 115 | reset: 0x50FFFF48 116 | type: u32 117 | fields: 118 | - bit_range: [2, 0] 119 | name: RESERVED 120 | explaination: 121 | 0b000: Default 122 | _: Error 123 | - bit_range: [3, 3] 124 | name: CFG_RESET_EN 125 | description: "RST# external manual reset input pin enable" 126 | explaination: 127 | 0: Disable 128 | 1: Enable 129 | - bit_range: [4, 4] 130 | name: CFG_DEBUG_EN 131 | description: "Two-wire simulation debug interface SWD enable" 132 | explaination: 133 | 0: Disable 134 | 1: Enable 135 | - bit_range: [5, 5] 136 | name: RESERVED 137 | explaination: 138 | 0: Default 139 | _: Error 140 | - bit_range: [6, 6] 141 | name: CFG_BOOT_EN 142 | description: "Bootloader enable" 143 | explaination: 144 | 0: Disable 145 | 1: Enable 146 | - bit_range: [7, 7] 147 | name: CFG_ROM_READ 148 | description: "Code and data protection mode in FlashROM" 149 | explaination: 150 | 0: Disable the programmer to read out, and keep the program secret 151 | 1: Read enable 152 | - bit_range: [27, 8] 153 | name: RESERVED 154 | explaination: 155 | 0xFFFF: Default 156 | _: Error 157 | - bit_range: [31, 28] 158 | name: VALID_SIG 159 | description: "Configuration information valid flag, fixed value" 160 | explaination: 161 | 0b0101: Valid 162 | _: Error 163 | 164 | variants: 165 | # Boot pin for CH571F: PB22(Default) PB11 166 | # Boot pin for CH571D: PB7(Default) PB11 167 | # CH571K: No boot pin 168 | - name: CH571 169 | chip_id: 0x71 170 | flash_size: 196608 171 | eeprom_size: 32768 172 | eeprom_start_addr: 196608 173 | config_registers: *config_registers_ch571_ch573 174 | 175 | - name: CH573 176 | chip_id: 0x73 177 | flash_size: 458752 178 | eeprom_size: 32768 179 | eeprom_start_addr: 458752 180 | config_registers: *config_registers_ch571_ch573 181 | 182 | - name: CH577 183 | chip_id: 0x77 184 | flash_size: 131072 185 | eeprom_size: 2048 186 | eeprom_start_addr: 131072 187 | 188 | - name: CH578 189 | chip_id: 0x58 190 | flash_size: 163840 191 | eeprom_size: 2048 192 | eeprom_start_addr: 163840 193 | 194 | - name: CH579 195 | chip_id: 0x79 196 | flash_size: 256000 197 | eeprom_size: 2048 198 | eeprom_start_addr: 256000 199 | config_registers: *config_registers_ch579 200 | -------------------------------------------------------------------------------- /devices/0x14-CH32F103.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32F103 Series 3 | mcu_type: 4 4 | device_type: 0x14 5 | support_net: false 6 | support_usb: true 7 | support_serial: true 8 | description: CH32F103 (Cortex-M3) Series 9 | config_registers: 10 | - offset: 0x00 11 | name: RDPR_USER 12 | reset: 0x00FF5AA5 13 | fields: 14 | # byte 0 15 | - bit_range: [7, 0] 16 | name: RDPR 17 | description: Read Protection. 0xA5 for unprotected 18 | explaination: 19 | 0xa5: Unprotected 20 | _: Protected 21 | # byte 2 22 | - bit_range: [16, 16] 23 | name: IWDG_SW 24 | description: Independent watchdog (IWDG) hardware enable 25 | explaination: 26 | 1: IWDG enabled by the software, and disabled by hardware 27 | 0: IWDG enabled by the software (decided along with the LSI clock) 28 | - bit_range: [17, 17] 29 | name: STOP_RST 30 | description: System reset control under the stop mode 31 | explaination: 32 | 1: Disable 33 | 0: Enable 34 | - bit_range: [18, 18] 35 | name: STANDBY_RST 36 | description: System reset control under the standby mode, STANDY_RST 37 | explaination: 38 | 1: Disable, entering standby-mode without RST 39 | 0: Enable 40 | - offset: 0x04 41 | name: DATA 42 | description: Customizable 2 byte data, DATA0, nDATA0, DATA1, nDATA1 43 | reset: 0xFF00FF00 44 | type: u32 45 | fields: 46 | - bit_range: [7, 0] 47 | name: DATA0 48 | - bit_range: [23, 16] 49 | name: DATA1 50 | - offset: 0x08 51 | name: WRP 52 | # Each bit represents 4K bytes (16 pages) to store the write protection status 53 | description: Flash memory write protection status 54 | type: u32 55 | reset: 0xFFFFFFFF 56 | explaination: 57 | 0xFFFFFFFF: Unprotected 58 | _: Some 4K sections are protected 59 | variants: 60 | - name: CH32F103C6T6 61 | chip_id: 0x32 62 | flash_size: 32K 63 | - name: CH32F103(C8T6|C8U6|R8T6) 64 | chip_id: 0x33 65 | alt_chip_ids: ["ALL"] 66 | flash_size: 64K 67 | -------------------------------------------------------------------------------- /devices/0x15-CH32V103.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32V103 Series 3 | mcu_type: 5 4 | device_type: 0x15 5 | support_net: false 6 | support_usb: true 7 | support_serial: true 8 | description: CH32V103 (RISC-V3A) Series 9 | config_registers: 10 | - offset: 0x00 11 | name: RDPR_USER 12 | description: RDPR, nRDPR, USER, nUSER 13 | reset: 0x00FF5AA5 14 | fields: 15 | # byte 0 16 | - bit_range: [7, 0] 17 | name: RDPR 18 | description: Read Protection. 0xA5 for unprotected, otherwise read-protected(ignoring WRP) 19 | explaination: 20 | 0xa5: Unprotected 21 | _: Protected 22 | # byte 2 23 | - bit_range: [16, 16] 24 | name: IWDG_SW 25 | description: Independent watchdog (IWDG) hardware enable 26 | explaination: 27 | 1: IWDG enabled by the software, and disabled by hardware 28 | 0: IWDG enabled by the software (decided along with the LSI clock) 29 | - bit_range: [17, 17] 30 | name: STOP_RST 31 | description: System reset control under the stop mode 32 | explaination: 33 | 1: Disable 34 | 0: Enable 35 | - bit_range: [18, 18] 36 | name: STANDBY_RST 37 | description: System reset control under the standby mode, STANDY_RST 38 | explaination: 39 | 1: Disable, entering standby-mode without RST 40 | 0: Enable 41 | - offset: 0x04 42 | name: DATA 43 | description: Customizable 2 byte data, DATA0, nDATA0, DATA1, nDATA1 44 | reset: 0xFF00FF00 45 | type: u32 46 | fields: 47 | - bit_range: [7, 0] 48 | name: DATA0 49 | - bit_range: [23, 16] 50 | name: DATA1 51 | - offset: 0x08 52 | name: WRP 53 | # Each bit represents 4K bytes (16 pages) to store the write protection status 54 | description: Flash memory write protection status 55 | type: u32 56 | reset: 0xFFFFFFFF 57 | explaination: 58 | 0xFFFFFFFF: Unprotected 59 | _: Some 4K sections are protected 60 | variants: 61 | - name: CH32V103C6T6 62 | chip_id: 0x32 63 | flash_size: 32K 64 | - name: CH32V103(C8T6|C8U6|R8T6) 65 | chip_id: 0x33 66 | # 63 *8*6 67 | alt_chip_ids: ["ALL"] 68 | flash_size: 64K 69 | -------------------------------------------------------------------------------- /devices/0x16-CH58x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH58x Series 3 | mcu_type: 6 4 | device_type: 0x16 5 | support_usb: true 6 | support_serial: true 7 | support_net: false 8 | description: CH58x (RISC-V4A BLE 5.3) Series 9 | config_registers: 10 | - offset: 0x00 11 | name: RESERVED 12 | description: Reserved 32-bit word 13 | reset: 0xFFFFFFFF 14 | type: u32 15 | - offset: 0x04 16 | name: WPROTECT 17 | reset: 0xFFFFFFFF 18 | type: u32 19 | fields: 20 | - bit_range: [0, 0] 21 | name: NO_KEY_SERIAL_DOWNLOAD 22 | description: Turn on No-key serial port download 23 | explaination: 24 | 1: Enable 25 | 0: Disable 26 | - bit_range: [1, 1] 27 | name: DOWNLOAD_CFG 28 | explaination: 29 | 1: PB22(Default set) 30 | 0: PB11 31 | # TODO: parse write protect address 32 | - offset: 0x08 33 | name: USER_CFG 34 | description: User non-volatile configuration 35 | # CFG_DEBUG_EN=1、CFG_RESET_EN=0、CFG_ROM_READ=1 36 | # Enable 2-wire debug 37 | # reset: 0xd73f0f4d 38 | # See-also: #26 39 | reset: 0xd50fff4f # WeActStudio default 40 | enable_debug: 0x450f3fd7 # WCHISPTool default 41 | type: u32 42 | fields: 43 | - bit_range: [2, 0] 44 | name: RESERVED 45 | explaination: 46 | 0b101: Default 47 | _: Changed 48 | - bit_range: [3, 3] 49 | name: CFG_RESET_EN 50 | description: "RST# external manual reset input pin enable" 51 | explaination: 52 | 0: Disable 53 | 1: Enable 54 | - bit_range: [4, 4] 55 | name: CFG_DEBUG_EN 56 | description: "Two-wire simulation debug interface SWD enable" 57 | explaination: 58 | 0: Disable 59 | 1: Enable 60 | - bit_range: [5, 5] 61 | name: RESERVED 62 | - bit_range: [6, 6] 63 | name: CFG_BOOT_EN 64 | description: "Bootloader enable" 65 | explaination: 66 | 0: Disable 67 | 1: Enable 68 | - bit_range: [7, 7] 69 | name: CFG_ROM_READ 70 | description: "Code and data protection mode in FlashROM" 71 | explaination: 72 | 0: Disable the programmer to read out, and keep the program secret 73 | 1: Read enable 74 | - bit_range: [27, 8] 75 | name: RESERVED 76 | explaination: 77 | 0xFFF0F: Default 78 | _: Changed 79 | - bit_range: [31, 28] 80 | name: VALID_SIG 81 | description: "Configuration information valid flag, fixed value" 82 | explaination: 83 | 0b0100: Valid 84 | _: Error 85 | 86 | variants: 87 | - name: CH581 88 | chip_id: 0x81 89 | flash_size: 196608 # 192KB 90 | eeprom_size: 32768 91 | eeprom_start_addr: 196608 92 | 93 | - name: CH582 94 | chip_id: 0x82 95 | flash_size: 458752 # 448KB 96 | eeprom_size: 32768 97 | eeprom_start_addr: 458752 98 | 99 | - name: CH583 100 | chip_id: 0x83 101 | flash_size: 458752 102 | eeprom_size: 557056 # 512KB 103 | eeprom_start_addr: 458752 104 | 105 | - name: CH584 106 | chip_id: 0x84 107 | flash_size: 448K 108 | eeprom_size: 96K 109 | eeprom_start_addr: 458752 110 | 111 | - name: CH585 112 | chip_id: 0x85 113 | flash_size: 448K 114 | eeprom_size: 128K 115 | eeprom_start_addr: 458752 116 | -------------------------------------------------------------------------------- /devices/0x17-CH32V30x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32V30x Series 3 | mcu_type: 7 4 | device_type: 0x17 5 | support_net: false 6 | support_usb: true 7 | support_serial: true 8 | description: CH32V30x (RISC-V4F) Series 9 | config_registers: 10 | - offset: 0x00 11 | name: RDPR_USER 12 | description: RDPR, nRDPR, USER, nUSER 13 | reset: 0x00FF5AA5 14 | fields: 15 | - bit_range: [7, 0] 16 | name: RDPR 17 | description: Read Protection. 0xA5 for unprotected, otherwise read-protected(ignoring WRP) 18 | explaination: 19 | 0xa5: Unprotected 20 | _: Protected 21 | # byte 2, [0:0] + 16 22 | - bit_range: [16, 16] 23 | name: IWDG_SW 24 | description: Independent watchdog (IWDG) hardware enable 25 | explaination: 26 | 1: IWDG enabled by the software, and disabled by hardware 27 | 0: IWDG enabled by the software (decided along with the LSI clock) 28 | # [1:1] + 16 29 | - bit_range: [17, 17] 30 | name: STOP_RST 31 | description: System reset control under the stop mode 32 | explaination: 33 | 1: Disable 34 | 0: Enable 35 | # [2:2] + 16 36 | - bit_range: [18, 18] 37 | name: STANDBY_RST 38 | description: System reset control under the standby mode, STANDY_RST 39 | explaination: 40 | 1: Disable, entering standby-mode without RST 41 | 0: Enable 42 | # [7:6] + 16 43 | - bit_range: [23, 22] 44 | name: SRAM_CODE_MODE 45 | description: SRAM Code Mode 46 | explaination: 47 | 0b00: CODE-192KB + RAM-128KB 48 | 0b01: CODE-224KB + RAM-96KB 49 | 0b10: CODE-256KB + RAM-64KB 50 | 0b11: CODE-228KB + RAM-32KB 51 | - offset: 0x04 52 | name: DATA 53 | description: Customizable 2 byte data, DATA0, nDATA0, DATA1, nDATA1 54 | reset: 0xFF00FF00 55 | type: u32 56 | fields: 57 | - bit_range: [7, 0] 58 | name: DATA0 59 | - bit_range: [23, 16] 60 | name: DATA1 61 | - offset: 0x08 62 | name: WRP 63 | # Each bit represents 4K bytes (16 pages) to store the write protection status 64 | description: Flash memory write protection status 65 | type: u32 66 | reset: 0xFFFFFFFF 67 | explaination: 68 | 0xFFFFFFFF: Unprotected 69 | _: Some 4K sections are protected 70 | variants: 71 | - name: CH32V305RBT6 72 | chip_id: 0x50 73 | flash_size: 128K 74 | - name: CH32V307VCT6 75 | chip_id: 0x70 76 | flash_size: 256K 77 | - name: CH32V307RCT6 78 | chip_id: 0x71 79 | flash_size: 256K 80 | - name: CH32V307WCU6 81 | chip_id: 0x73 82 | flash_size: 256K 83 | - name: CH32V303VCT6 84 | chip_id: 0x30 85 | flash_size: 256K 86 | - name: CH32V303RCT6 87 | chip_id: 0x31 88 | flash_size: 256K 89 | - name: CH32V303RBT6 90 | chip_id: 0x32 91 | flash_size: 128K 92 | - name: CH32V303CBT6 93 | chip_id: 0x33 94 | flash_size: 128K 95 | -------------------------------------------------------------------------------- /devices/0x18-CH32F20x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32F20x D8/D8C/D8W Series 3 | mcu_type: 8 4 | device_type: 0x18 5 | support_net: false 6 | support_usb: true 7 | support_serial: true 8 | # Specific D8, D8C, D8W 9 | description: CH32F20x High density general-purpose(F203), Connectivity(F205), Interconnectivity(F207), Wireless(F208) 10 | variants: 11 | - name: CH32F205RBT6 12 | chip_id: 0x50 13 | flash_size: 128K 14 | - name: CH32F207VCT6 15 | chip_id: 0x70 16 | flash_size: 256K 17 | - name: CH32F208WBU6 18 | chip_id: 0x80 19 | flash_size: 128K 20 | - name: CH32F208RBT6 21 | chip_id: 0x81 22 | flash_size: 128K 23 | - name: CH32F203VCT6 24 | chip_id: 0x30 25 | flash_size: 256K 26 | - name: CH32F203RCT6 27 | chip_id: 0x31 28 | flash_size: 256K 29 | - name: CH32F203CBT6 30 | chip_id: 0x33 31 | flash_size: 128K 32 | 33 | -------------------------------------------------------------------------------- /devices/0x19-CH32V20x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32V20x Series 3 | mcu_type: 9 4 | device_type: 0x19 5 | support_net: false 6 | support_usb: true 7 | support_serial: true 8 | description: CH32V20x (RISC-V4B/V4C) Series 9 | config_registers: 10 | # Ref: section "32.6 User option bytes" of RM manual 11 | - offset: 0x00 12 | name: RDPR_USER 13 | description: RDPR, nRDPR, USER, nUSER 14 | reset: 0x00FF5AA5 15 | type: u32 16 | fields: # TODO 17 | - bit_range: [7, 0] 18 | name: RDPR 19 | description: Read Protection. 0xA5 for unprotected, otherwise read-protected(ignoring WRP) 20 | explaination: 21 | 0xa5: Unprotected 22 | _: Protected 23 | - bit_range: [16, 16] 24 | name: IWDG_SW 25 | description: Independent watchdog (IWDG) hardware enable 26 | explaination: 27 | 1: IWDG enabled by the software, and disabled by hardware 28 | 0: IWDG enabled by the software (decided along with the LSI clock) 29 | # [1:1] + 16 30 | - bit_range: [17, 17] 31 | name: STOP_RST 32 | description: System reset control under the stop mode 33 | explaination: 34 | 1: Disable 35 | 0: Enable 36 | # [2:2] + 16 37 | - bit_range: [18, 18] 38 | name: STANDBY_RST 39 | description: System reset control under the standby mode, STANDY_RST 40 | explaination: 41 | 1: Disable, entering standby-mode without RST 42 | 0: Enable 43 | # [7:6] + 16 44 | - bit_range: [23, 22] 45 | name: SRAM_CODE_MODE 46 | description: SRAM Code Mode 47 | explaination: 48 | 0b00: CODE-192KB + RAM-128KB / CODE-128KB + RAM-64KB depending on the chip 49 | 0b01: CODE-224KB + RAM-96KB / CODE-144KB + RAM-48KB depending on the chip 50 | 0b10: CODE-256KB + RAM-64KB / CODE-160KB + RAM-32KB depending on the chip 51 | 0b11: CODE-228KB + RAM-32KB / CODE-160KB + RAM-32KB depending on the chip 52 | - offset: 0x04 53 | name: DATA 54 | description: Customizable 2 byte data, DATA0, nDATA0, DATA1, nDATA1 55 | reset: 0xFF00FF00 56 | type: u32 57 | fields: 58 | - bit_range: [7, 0] 59 | name: DATA0 60 | - bit_range: [23, 16] 61 | name: DATA1 62 | - offset: 0x08 63 | name: WRP 64 | # Each bit represents 4K bytes (16 pages) to store the write protection status 65 | description: Flash memory write protection status 66 | type: u32 67 | reset: 0xFFFFFFFF 68 | explaination: 69 | 0xFFFFFFFF: Unprotected 70 | _: Some 4K sections are protected 71 | 72 | # chip_id 0x8X for V4B, chip_id 0x3X for V4C 73 | variants: 74 | - name: CH32V208WBU6 75 | chip_id: 0x80 76 | flash_size: 128K 77 | - name: CH32V208RBT6 78 | chip_id: 0x81 79 | flash_size: 128K 80 | - name: CH32V208CBU6 81 | chip_id: 0x82 82 | flash_size: 128K 83 | - name: CH32V208GBU6 84 | chip_id: 0x83 85 | flash_size: 128K 86 | - name: CH32V203C8U6 87 | chip_id: 0x30 88 | flash_size: 64K 89 | - name: CH32V203C8T6 90 | chip_id: 0x31 91 | flash_size: 64K 92 | - name: CH32V203K8T6 93 | chip_id: 0x32 94 | flash_size: 64K 95 | - name: CH32V203C6T6 96 | chip_id: 0x33 97 | flash_size: 32K 98 | - name: CH32V203RBT6 99 | chip_id: 0x34 100 | flash_size: 128K 101 | - name: CH32V203K6T6 102 | chip_id: 0x35 103 | flash_size: 32K 104 | - name: CH32V203G6U6 105 | chip_id: 0x36 106 | flash_size: 32K 107 | - name: CH32V203F6P6 108 | chip_id: 0x37 109 | alt_chip_ids: [0x39] 110 | flash_size: 32K 111 | # FIXME: missing 0x38 112 | - name: CH32V203G8R6 113 | chip_id: 0x3b 114 | flash_size: 64K 115 | -------------------------------------------------------------------------------- /devices/0x20-CH32F20x-Compact.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32F20x D6 Series 3 | # NOTE: mcu_type is hex-encoded, not "10" in base-10 4 | mcu_type: 0x10 5 | device_type: 0x20 6 | support_net: false 7 | support_usb: true 8 | support_serial: true 9 | description: CH32F20x Low-and-medium-density general-purpose Cortex-M3 (Specific D6) 10 | variants: 11 | - name: CH32F203C8U6 12 | chip_id: 0x30 13 | flash_size: 64K 14 | - name: CH32F203C8T6 15 | chip_id: 0x31 16 | flash_size: 64K 17 | # FIXME: CH32F203K8T6 is not listed in config file, this is a guess 18 | - name: CH32F203K8T6 19 | chip_id: 0x32 20 | flash_size: 64K 21 | - name: CH32F203C6T6 22 | chip_id: 0x33 23 | flash_size: 32K 24 | 25 | -------------------------------------------------------------------------------- /devices/0x21-CH32V00x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32V00x Series 3 | mcu_type: 0x11 4 | device_type: 0x21 5 | support_net: false 6 | support_usb: false 7 | support_serial: true 8 | description: CH32V00x (RISC-V2A/V2C) Series 9 | config_registers: 10 | # Ref: section 16.5 (CH32V003) & 18.5 (CH32V00x) "User Option Bytes" of RM manual 11 | - offset: 0x00 12 | name: RDPR_USER 13 | description: RDPR, nRDPR, USER, nUSER 14 | reset: 0x08F75AA5 15 | type: u32 16 | fields: 17 | - bit_range: [7, 0] 18 | name: RDPR 19 | description: Read Protection. 0xA5 for unprotected, otherwise read-protected (ignoring WRPR) 20 | explaination: 21 | 0xa5: Unprotected 22 | _: Protected 23 | - bit_range: [16, 16] 24 | name: IWDG_SW 25 | description: Independent watchdog (IWDG) hardware enable 26 | explaination: 27 | 1: IWDG enabled by software, and not enabled by hardware 28 | 0: IWDG enabled by hardware (along with the LSI clock) 29 | - bit_range: [18, 18] 30 | name: STANDBY_RST 31 | description: System reset control under the standby mode 32 | explaination: 33 | 1: Disabled, entering standby-mode without RST 34 | 0: Enabled 35 | - bit_range: [20, 19] 36 | name: RST_MODE 37 | description: External pin PD7 reset mode 38 | explaination: 39 | 0b00: Ignoring pin states within 128us after turning on the multiplexing function 40 | 0b01: Ignoring pin states within 1ms after turning on the multiplexing function 41 | 0b10: Ignoring pin states within 12ms after turning on the multiplexing function 42 | 0b11: Multiplexing function off, PD7 for I/O function 43 | - bit_range: [21, 21] 44 | name: START_MODE 45 | description: Power-on startup mode 46 | explaination: 47 | 1: Start from BOOT area 48 | 0: Start from user CODE area 49 | - offset: 0x04 50 | name: DATA 51 | description: Customizable 2 byte data, DATA0, nDATA0, DATA1, nDATA1 52 | reset: 0xFF00FF00 53 | type: u32 54 | fields: 55 | - bit_range: [7, 0] 56 | name: DATA0 57 | - bit_range: [23, 16] 58 | name: DATA1 59 | - offset: 0x08 60 | name: WRPR 61 | # Each bit is used to control the write-protect status of sectors as follows: 62 | # CH32V003: 1 sector (1K/sector), max 16K (WRPR3/4 reserved) 63 | # CH32V002/004/005/006/007: 2 sectors (1K/sector), max 64K 64 | description: Flash memory write protection status 65 | type: u32 66 | reset: 0xFFFFFFFF 67 | explaination: 68 | 0xFFFFFFFF: Unprotected 69 | _: Some 1K sections are protected 70 | variants: 71 | - name: CH32V002A4M6 72 | chip_id: 0x22 73 | flash_size: 16K 74 | - name: CH32V002D4U6 75 | chip_id: 0x23 76 | flash_size: 16K 77 | - name: CH32V002F4P6 78 | chip_id: 0x20 79 | flash_size: 16K 80 | - name: CH32V002F4U6 81 | chip_id: 0x21 82 | flash_size: 16K 83 | - name: CH32V002J4M6 84 | chip_id: 0x24 85 | flash_size: 16K 86 | - name: CH32V003A4M6 87 | chip_id: 0x32 88 | flash_size: 16K 89 | - name: CH32V003F4P6 90 | chip_id: 0x30 91 | flash_size: 16K 92 | - name: CH32V003F4U6 93 | chip_id: 0x31 94 | flash_size: 16K 95 | - name: CH32V003J4M6 96 | chip_id: 0x33 97 | flash_size: 16K 98 | - name: CH32V004F6P1 99 | chip_id: 0x40 100 | flash_size: 32K 101 | - name: CH32V004F6U1 102 | chip_id: 0x41 103 | flash_size: 32K 104 | - name: CH32V005D6U6 105 | chip_id: 0x53 106 | flash_size: 32K 107 | - name: CH32V005E6R6 108 | chip_id: 0x50 109 | flash_size: 32K 110 | - name: CH32V005F6P6 111 | chip_id: 0x52 112 | flash_size: 32K 113 | - name: CH32V005F6U6 114 | chip_id: 0x51 115 | flash_size: 32K 116 | - name: CH32V006E8R6 117 | chip_id: 0x61 118 | flash_size: 62K 119 | - name: CH32V006F8P6 120 | chip_id: 0x63 121 | flash_size: 62K 122 | - name: CH32V006F8U6 123 | chip_id: 0x62 124 | flash_size: 62K 125 | - name: CH32V006K8U6 126 | chip_id: 0x60 127 | flash_size: 62K 128 | - name: CH32V007E8R6 129 | chip_id: 0x71 130 | flash_size: 62K 131 | - name: CH32V007K8U6 132 | chip_id: 0x72 133 | flash_size: 62K 134 | - name: CH32M007G8R6 135 | chip_id: 0x70 136 | flash_size: 62K 137 | # TODO: add CH32M007E8R6, CH32M007E8U6 when chip IDs known. 138 | -------------------------------------------------------------------------------- /devices/0x22-CH59x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH59x Series 3 | mcu_type: 0x12 4 | device_type: 0x22 5 | support_usb: true 6 | support_serial: true 7 | support_net: false 8 | description: CH59x (RISC-V4C BLE 5.4) Series 9 | config_registers: 10 | - offset: 0x00 11 | name: RESERVED 12 | description: Reserved 32-bit word 13 | reset: 0xFFFFFFFF 14 | type: u32 15 | - offset: 0x04 16 | name: WPROTECT 17 | reset: 0xFFFFFFFF 18 | type: u32 19 | fields: 20 | - bit_range: [0, 0] 21 | name: NO_KEY_SERIAL_DOWNLOAD 22 | description: Turn on No-key serial port download 23 | explaination: 24 | 1: Enable 25 | 0: Disable 26 | - bit_range: [1, 1] 27 | name: DOWNLOAD_CFG 28 | explaination: 29 | 1: PB22(Default set) 30 | 0: PB11 31 | - offset: 0x08 32 | name: USER_CFG 33 | description: User config register 34 | reset: 0x4FFF0FD5 35 | type: u32 36 | fields: 37 | - bit_range: [2, 0] 38 | name: RESERVED 39 | explaination: 40 | 0b101: Default 41 | _: Error 42 | - bit_range: [3, 3] 43 | name: CFG_RESET_EN 44 | description: "RST# external manual reset input pin enable" 45 | explaination: 46 | 0: Disable 47 | 1: Enable 48 | - bit_range: [4, 4] 49 | name: CFG_DEBUG_EN 50 | description: "Two-wire simulation debug interface SWD enable" 51 | explaination: 52 | 0: Disable 53 | 1: Enable 54 | - bit_range: [5, 5] 55 | name: RESERVED 56 | explaination: 57 | 0: Default 58 | _: Error 59 | - bit_range: [6, 6] 60 | name: CFG_BOOT_EN 61 | description: "Bootloader enable" 62 | explaination: 63 | 0: Disable 64 | 1: Enable 65 | - bit_range: [7, 7] 66 | name: CFG_ROM_READ 67 | description: "Code and data protection mode in FlashROM" 68 | explaination: 69 | 0: Disable the programmer to read out, and keep the program secret 70 | 1: Read enable 71 | - bit_range: [27, 8] 72 | name: RESERVED 73 | explaination: 74 | 0xFF0F: Default 75 | _: Error 76 | - bit_range: [31, 28] 77 | name: VALID_SIG 78 | description: "Configuration information valid flag, fixed value" 79 | explaination: 80 | 0b0100: Valid 81 | _: Error 82 | 83 | variants: 84 | - name: CH591 85 | chip_id: 0x91 # 145 86 | flash_size: 196608 87 | eeprom_size: 32768 88 | 89 | - name: CH592 90 | chip_id: 0x92 # 146 91 | flash_size: 458752 92 | eeprom_size: 32768 93 | 94 | -------------------------------------------------------------------------------- /devices/0x23-CH32X03x.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32X03x Series 3 | # NOTE: mcu_type is hex-encoded, not "10" in base-10 4 | mcu_type: 0x13 5 | device_type: 0x23 6 | support_net: false 7 | support_usb: true 8 | support_serial: true 9 | description: CH32X03x RISC-V4C Series 10 | # RM Section 20.5 11 | config_registers: 12 | - offset: 0x00 13 | name: RDPR_USER 14 | description: RDPR, nRDPR, USER, nUSER 15 | reset: 0xFFFF5AA5 16 | fields: 17 | - bit_range: [7, 0] 18 | name: RDPR 19 | description: Read Protection. 0xA5 for unprotected, otherwise read-protected (ignoring WRPR) 20 | explaination: 21 | 0xa5: Unprotected 22 | _: Protected 23 | # byte 2, [0:0] + 16 24 | - bit_range: [16, 16] 25 | name: IWDG_SW 26 | description: Independent watchdog (IWDG) hardware enable 27 | explaination: 28 | 1: IWDG enabled by the software, and disabled by hardware 29 | 0: IWDG enabled by the software (decided along with the LSI clock) 30 | # [1:1] + 16 31 | - bit_range: [17, 17] 32 | name: STOP_RST 33 | description: System reset control under the stop mode 34 | explaination: 35 | 1: Disabled 36 | 0: Enabled 37 | # [2:2] + 16 38 | - bit_range: [18, 18] 39 | name: STANDBY_RST 40 | description: System reset control under the standby mode 41 | explaination: 42 | 1: Disabled, entering standby-mode without RST 43 | 0: Enabled 44 | # [4:3] + 16 45 | - bit_range: [20, 19] 46 | name: RST_MODE 47 | description: Reset mode 48 | explaination: 49 | 0b00: Enable RST alternative function 50 | 0b11: Disable RST alternative function, use PA21/PC3/PB7 as GPIO 51 | _: Error 52 | - offset: 0x04 53 | name: DATA 54 | description: Customizable 2 byte data, DATA0, nDATA0, DATA1, nDATA1 55 | reset: 0xFF00FF00 56 | type: u32 57 | fields: 58 | - bit_range: [7, 0] 59 | name: DATA0 60 | - bit_range: [23, 16] 61 | name: DATA1 62 | - offset: 0x08 63 | name: WRPR 64 | # Each bit is used to control the write-protect status of 2 sectors (1K/sector), max 64K 65 | description: Flash memory write protection status 66 | type: u32 67 | reset: 0xFFFFFFFF 68 | explaination: 69 | 0xFFFFFFFF: Unprotected 70 | _: Some 1K sectors are protected 71 | variants: 72 | - name: CH32X035R8T6 73 | chip_id: 80 74 | flash_size: 62K 75 | - name: CH32X035C8T6 76 | chip_id: 81 77 | flash_size: 62K 78 | - name: CH32X035F8U6 79 | chip_id: 94 80 | flash_size: 62K 81 | - name: CH32X035G8U6 82 | chip_id: 86 83 | flash_size: 62K 84 | - name: CH32X035G8R6 85 | chip_id: 91 86 | flash_size: 62K 87 | - name: CH32X035F7P6 88 | chip_id: 87 89 | flash_size: 49152 90 | - name: CH32X033F8P6 91 | chip_id: 90 # 0x5a 92 | flash_size: 62K 93 | -------------------------------------------------------------------------------- /devices/0x24-CH643.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH643 Series 3 | # NOTE: mcu_type is hex-encoded, not "10" in base-10 4 | mcu_type: 0x14 5 | device_type: 0x24 6 | support_net: false 7 | support_usb: true 8 | support_serial: true 9 | description: CH643 RISC-V4C RGB Display Driver Series 10 | variants: 11 | # NOTE: These chips share the same flash size, so we can use a single variant 12 | - name: CH643 13 | # 48 - CH643W 14 | # 49 - CH643Q 15 | # 51 - CH643L 16 | # 52 - CH643U 17 | chip_id: 48 18 | alt_chip_ids: ["ALL"] 19 | flash_size: 63488 20 | 21 | -------------------------------------------------------------------------------- /devices/0x25-CH32L103.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CH32L103 Series 3 | mcu_type: 0x15 4 | device_type: 0x25 5 | support_net: false 6 | support_usb: true 7 | support_serial: true 8 | description: CH32L103 (RISC-V4C) Series 9 | config_registers: 10 | variants: 11 | - name: CH32L103C8U6 12 | chip_id: 48 13 | flash_size: 64K 14 | - name: CH32L103C8T6 15 | chip_id: 49 16 | flash_size: 64K 17 | - name: CH32L103F8P6 18 | chip_id: 58 19 | flash_size: 64K 20 | - name: CH32L103G8R6 21 | chip_id: 59 22 | flash_size: 64K 23 | - name: CH32L103K8U6 24 | chip_id: 50 25 | flash_size: 64K 26 | - name: CH32L103F8U6 27 | chip_id: 61 28 | flash_size: 64K 29 | - name: CH32L103F7P6 30 | chip_id: 55 31 | flash_size: 48K 32 | -------------------------------------------------------------------------------- /devices/SCHEMA.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 3 | mcu_type: 4 | device_type: # normally this is mcy_type + 0x10 5 | support_usb: 6 | support_serial: 7 | support_net: 8 | description: 9 | config_registers: 10 | # registers are parsed in LE mode 11 | - offset: 12 | name: 13 | description: 14 | reset: # a u32 value, used to reset chip config, like unprotect 15 | fields: 16 | - bit_range: [7, 0] # inclusive range, [MSB, LSB] 17 | name: 18 | description: 19 | explaination: 20 | 0xa5: Unprotected # an explaination to field value 21 | _: Protected # matches all remaining cases 22 | variants: 23 | - name: 24 | chip_id: 0x30 25 | alt_chip_ids: ["ALL"] # special fix to probe all chip variants 26 | flash_size: 64K # 0x10000, 64KiB, 64KB, 64K 27 | eeprom_size: 32K 28 | eeprom_start_addr: 0x0 29 | support_usb: true # config can overwrite faimily config 30 | support_serial: true 31 | support_net: false 32 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants about protocol and devices. 2 | 3 | pub const MAX_PACKET_SIZE: usize = 64; 4 | pub const SECTOR_SIZE: usize = 1024; 5 | 6 | /// All readable and writable registers. 7 | /// - `RDPR`: Read Protection 8 | /// - `USER`: User Config Byte (normally in Register Map datasheet) 9 | /// - `WPR`: Write Protection Mask, 1=unprotected, 0=protected 10 | /// 11 | /// | BYTE0 | BYTE1 | BYTE2 | BYTE3 | 12 | /// |--------|--------|--------|--------| 13 | /// | RDPR | nRDPR | USER | nUSER | 14 | /// | DATA0 | nDATA0 | DATA1 | nDATA1 | 15 | /// | WPR0 | WPR1 | WPR2 | WPR3 | 16 | pub const CFG_MASK_RDPR_USER_DATA_WPR: u8 = 0x07; 17 | /// Bootloader version, in the format of `[0x00, major, minor, 0x00]` 18 | pub const CFG_MASK_BTVER: u8 = 0x08; 19 | /// Device Unique ID 20 | pub const CFG_MASK_UID: u8 = 0x10; 21 | /// All mask bits of CFGs 22 | pub const CFG_MASK_ALL: u8 = 0x1f; 23 | 24 | pub mod commands { 25 | pub const IDENTIFY: u8 = 0xa1; 26 | pub const ISP_END: u8 = 0xa2; 27 | pub const ISP_KEY: u8 = 0xa3; 28 | pub const ERASE: u8 = 0xa4; 29 | pub const PROGRAM: u8 = 0xa5; 30 | pub const VERIFY: u8 = 0xa6; 31 | pub const READ_CONFIG: u8 = 0xa7; 32 | pub const WRITE_CONFIG: u8 = 0xa8; 33 | pub const DATA_ERASE: u8 = 0xa9; 34 | pub const DATA_PROGRAM: u8 = 0xaa; 35 | pub const DATA_READ: u8 = 0xab; 36 | pub const WRITE_OTP: u8 = 0xc3; 37 | pub const READ_OTP: u8 = 0xc4; 38 | pub const SET_BAUD: u8 = 0xc5; 39 | } 40 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | //! MCU Chip definition, with chip-specific or chip-family-specific flags 2 | use std::collections::BTreeMap; 3 | 4 | use anyhow::Result; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | /// MCU Family 8 | #[derive(Debug, Clone, Serialize, Deserialize)] 9 | pub struct ChipFamily { 10 | pub name: String, 11 | pub mcu_type: u8, 12 | pub device_type: u8, 13 | support_usb: Option, 14 | support_serial: Option, 15 | support_net: Option, 16 | pub description: String, 17 | pub variants: Vec, 18 | #[serde(default)] 19 | pub config_registers: Vec, 20 | } 21 | 22 | impl ChipFamily { 23 | fn validate(&self) -> Result<()> { 24 | for variant in &self.variants { 25 | variant.validate()?; 26 | } 27 | for register in &self.config_registers { 28 | register.validate()?; 29 | } 30 | Ok(()) 31 | } 32 | } 33 | 34 | /// Represents an MCU chip 35 | #[derive(Debug, Clone, Serialize, Deserialize)] 36 | pub struct Chip { 37 | /// Chip's name, without variants surfix 38 | pub name: String, 39 | pub chip_id: u8, 40 | #[serde(default, deserialize_with = "parse_alt_chip_id_or_all_marker")] 41 | alt_chip_ids: Vec, 42 | 43 | #[serde(default)] 44 | pub mcu_type: u8, 45 | #[serde(default)] 46 | pub device_type: u8, 47 | 48 | #[serde(deserialize_with = "parse_address_and_offset")] 49 | pub flash_size: u32, 50 | #[serde(default, deserialize_with = "parse_address_and_offset")] 51 | pub eeprom_size: u32, 52 | 53 | #[serde(default, deserialize_with = "parse_address_and_offset")] 54 | pub eeprom_start_addr: u32, 55 | 56 | support_net: Option, 57 | support_usb: Option, 58 | support_serial: Option, 59 | 60 | #[serde(default)] 61 | pub config_registers: Vec, 62 | } 63 | 64 | impl ::std::fmt::Display for Chip { 65 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { 66 | write!( 67 | f, 68 | "{}[0x{:02x}{:02x}]", 69 | self.name, 70 | self.chip_id, 71 | self.device_type(), 72 | ) 73 | } 74 | } 75 | 76 | impl Chip { 77 | pub fn validate(&self) -> Result<()> { 78 | for reg in &self.config_registers { 79 | reg.validate()?; 80 | } 81 | Ok(()) 82 | } 83 | } 84 | 85 | /// A u32 config register, with reset values. 86 | /// 87 | /// The reset value is NOT the value of the register when the device is reset, 88 | /// but the value of the register when the device is in the flash-able mode. 89 | /// 90 | /// Read in LE mode. 91 | #[derive(Debug, Clone, Serialize, Deserialize)] 92 | pub struct ConfigRegister { 93 | pub offset: usize, 94 | pub name: String, 95 | #[serde(default)] 96 | pub description: String, 97 | pub reset: Option, 98 | pub enable_debug: Option, 99 | #[serde(default)] 100 | pub explaination: BTreeMap, 101 | #[serde(default)] 102 | pub fields: Vec, 103 | } 104 | 105 | impl ConfigRegister { 106 | fn validate(&self) -> Result<()> { 107 | if self.offset % 4 != 0 { 108 | anyhow::bail!("Config register offset must be 4-byte aligned"); 109 | } 110 | for field in &self.fields { 111 | field.validate()?; 112 | } 113 | Ok(()) 114 | } 115 | } 116 | 117 | /// A range of bits in a register, with a name and a description 118 | #[derive(Debug, Clone, Serialize, Deserialize)] 119 | pub struct RegisterField { 120 | // RangeInclusive is not supported well since serde_yaml 0.9 121 | pub bit_range: Vec, 122 | pub name: String, 123 | #[serde(default)] 124 | pub description: String, 125 | // NOTE: use BTreeMap for strict ordering for digits and `_` 126 | #[serde(default)] 127 | pub explaination: BTreeMap, 128 | } 129 | 130 | impl RegisterField { 131 | fn validate(&self) -> Result<()> { 132 | if self.bit_range.len() != 2 { 133 | anyhow::bail!("Invalid bit range: {:?}", self.bit_range); 134 | } 135 | if self.bit_range[0] < self.bit_range[1] { 136 | anyhow::bail!("Invalid bit range: {:?}", self.bit_range); 137 | } 138 | Ok(()) 139 | } 140 | } 141 | 142 | pub struct ChipDB { 143 | pub families: Vec, 144 | } 145 | 146 | impl ChipDB { 147 | pub fn load() -> Result { 148 | let families: Vec = vec![ 149 | serde_yaml::from_str(include_str!("../devices/0x10-CH56x.yaml"))?, 150 | serde_yaml::from_str(include_str!("../devices/0x11-CH55x.yaml"))?, 151 | serde_yaml::from_str(include_str!("../devices/0x12-CH54x.yaml"))?, 152 | serde_yaml::from_str(include_str!("../devices/0x13-CH57x.yaml"))?, 153 | serde_yaml::from_str(include_str!("../devices/0x14-CH32F103.yaml"))?, 154 | serde_yaml::from_str(include_str!("../devices/0x15-CH32V103.yaml"))?, 155 | serde_yaml::from_str(include_str!("../devices/0x16-CH58x.yaml"))?, 156 | serde_yaml::from_str(include_str!("../devices/0x17-CH32V30x.yaml"))?, 157 | serde_yaml::from_str(include_str!("../devices/0x18-CH32F20x.yaml"))?, 158 | serde_yaml::from_str(include_str!("../devices/0x19-CH32V20x.yaml"))?, 159 | serde_yaml::from_str(include_str!("../devices/0x20-CH32F20x-Compact.yaml"))?, 160 | serde_yaml::from_str(include_str!("../devices/0x21-CH32V00x.yaml"))?, 161 | serde_yaml::from_str(include_str!("../devices/0x22-CH59x.yaml"))?, 162 | serde_yaml::from_str(include_str!("../devices/0x23-CH32X03x.yaml"))?, 163 | serde_yaml::from_str(include_str!("../devices/0x24-CH643.yaml"))?, 164 | serde_yaml::from_str(include_str!("../devices/0x25-CH32L103.yaml"))?, 165 | ]; 166 | for family in &families { 167 | family.validate()?; 168 | } 169 | Ok(ChipDB { families }) 170 | } 171 | 172 | pub fn find_chip(&self, chip_id: u8, device_type: u8) -> Result { 173 | let family = self 174 | .families 175 | .iter() 176 | .find(|f| f.device_type == device_type) 177 | .ok_or_else(|| anyhow::format_err!("Device type of 0x{:02x} not found", device_type))?; 178 | 179 | let mut chip = family 180 | .variants 181 | .iter() 182 | .find(|c| c.chip_id == chip_id || c.alt_chip_ids.contains(&chip_id)) 183 | .cloned() 184 | .ok_or_else(|| { 185 | anyhow::format_err!( 186 | "Cannot find chip with id 0x{:02x} device_type 0x{:02x}", 187 | chip_id, 188 | device_type 189 | ) 190 | })?; 191 | // FIXME: better way to patch chip type? 192 | chip.mcu_type = family.mcu_type; 193 | chip.device_type = family.device_type; 194 | if chip_id != chip.chip_id { 195 | log::warn!("Find chip via alternative id: 0x{:02x}", chip.chip_id); 196 | chip.chip_id = chip_id; 197 | } 198 | if chip.support_net.is_none() { 199 | chip.support_net = family.support_net; 200 | } 201 | if chip.support_usb.is_none() { 202 | chip.support_usb = family.support_usb; 203 | } 204 | if chip.support_serial.is_none() { 205 | chip.support_serial = family.support_serial; 206 | } 207 | if chip.config_registers.is_empty() { 208 | chip.config_registers = family.config_registers.clone(); 209 | } 210 | Ok(chip) 211 | } 212 | } 213 | 214 | impl Chip { 215 | /// DeviceType = ChipSeries = SerialNumber = McuType + 0x10 216 | pub const fn device_type(&self) -> u8 { 217 | self.mcu_type + 0x10 218 | } 219 | 220 | /// Used when erasing 1K sectors 221 | pub const fn min_erase_sector_number(&self) -> u32 { 222 | if self.device_type() == 0x10 { 223 | 4 224 | } else { 225 | 8 226 | } 227 | } 228 | 229 | /// Used when calculating XOR key 230 | pub const fn uid_size(&self) -> usize { 231 | if self.device_type() == 0x11 { 232 | 4 233 | } else { 234 | 8 235 | } 236 | } 237 | 238 | /// Code flash protect support 239 | pub fn support_code_flash_protect(&self) -> bool { 240 | [0x14, 0x15, 0x17, 0x18, 0x19, 0x20].contains(&self.device_type()) 241 | } 242 | } 243 | 244 | fn parse_alt_chip_id_or_all_marker<'de, D>( 245 | deserializer: D, 246 | ) -> std::result::Result, D::Error> 247 | where 248 | D: serde::Deserializer<'de>, 249 | { 250 | let ids: Vec = serde::Deserialize::deserialize(deserializer)?; 251 | Ok(ids 252 | .into_iter() 253 | .flat_map(|i| { 254 | if i.starts_with("0x") || i.starts_with("0X") { 255 | vec![i[2..].parse().unwrap()] 256 | } else if i == "all" || i == "ALL" { 257 | (0..=0xff).into_iter().collect() 258 | } else { 259 | vec![i.parse().unwrap()] 260 | } 261 | }) 262 | .collect()) 263 | } 264 | 265 | fn parse_address_and_offset<'de, D>(deserializer: D) -> std::result::Result 266 | where 267 | D: serde::Deserializer<'de>, 268 | { 269 | let s: String = serde::Deserialize::deserialize(deserializer)?; 270 | if s.starts_with("0x") || s.starts_with("0X") { 271 | Ok(u32::from_str_radix(&s[2..], 16).expect(&format!("error while parsering {:?}", s))) 272 | } else if s.ends_with("K") { 273 | Ok(1024 274 | * u32::from_str_radix(&s[..s.len() - 1], 10) 275 | .expect(&format!("error while parsering {:?}", s))) 276 | } else if s.ends_with("KiB") { 277 | Ok(1024 278 | * u32::from_str_radix(&s[..s.len() - 3], 10) 279 | .expect(&format!("error while parsering {:?}", s))) 280 | } else if s.ends_with("KB") { 281 | Ok(1024 282 | * u32::from_str_radix(&s[..s.len() - 2], 10) 283 | .expect(&format!("error while parsering {:?}", s))) 284 | } else { 285 | // parse pure digits here 286 | Ok(s.parse().unwrap()) 287 | } 288 | } 289 | 290 | pub fn parse_number(s: &str) -> Option { 291 | if s.starts_with("0x") || s.starts_with("0X") { 292 | Some(u32::from_str_radix(&s[2..], 16).expect(&format!("error while parsering {:?}", s))) 293 | } else if s.starts_with("0b") || s.starts_with("0B") { 294 | Some(u32::from_str_radix(&s[2..], 2).expect(&format!("error while parsering {:?}", s))) 295 | } else { 296 | Some(s.parse().expect("must be a number")) 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/flashing.rs: -------------------------------------------------------------------------------- 1 | //! Chip flashing routine 2 | use std::time::Duration; 3 | 4 | use anyhow::{Ok, Result}; 5 | use indicatif::ProgressBar; 6 | use scroll::{Pread, Pwrite, LE}; 7 | 8 | use crate::{ 9 | constants::{CFG_MASK_ALL, CFG_MASK_RDPR_USER_DATA_WPR}, 10 | device::{parse_number, ChipDB}, 11 | transport::{SerialTransport, UsbTransport}, 12 | Baudrate, Chip, Command, Transport, 13 | }; 14 | 15 | pub struct Flashing<'a> { 16 | transport: Box, 17 | pub chip: Chip, 18 | /// Chip unique identifier 19 | chip_uid: Vec, 20 | // BTVER 21 | bootloader_version: [u8; 4], 22 | code_flash_protected: bool, 23 | } 24 | 25 | impl<'a> Flashing<'a> { 26 | pub fn get_chip(transport: &mut impl Transport) -> Result { 27 | let identify = Command::identify(0, 0); 28 | let resp = transport.transfer(identify)?; 29 | 30 | let chip_db = ChipDB::load()?; 31 | let chip = chip_db.find_chip(resp.payload()[0], resp.payload()[1])?; 32 | 33 | Ok(chip) 34 | } 35 | 36 | pub fn new_from_transport(mut transport: impl Transport + 'a) -> Result { 37 | let identify = Command::identify(0, 0); 38 | let resp = transport.transfer(identify)?; 39 | anyhow::ensure!(resp.is_ok(), "idenfity chip failed"); 40 | 41 | let chip = Flashing::get_chip(&mut transport)?; 42 | log::debug!("found chip: {}", chip); 43 | 44 | let read_conf = Command::read_config(CFG_MASK_ALL); 45 | let resp = transport.transfer(read_conf)?; 46 | anyhow::ensure!(resp.is_ok(), "read_config failed"); 47 | 48 | log::debug!("read_config: {}", hex::encode(&resp.payload()[2..])); 49 | let code_flash_protected = chip.support_code_flash_protect() && resp.payload()[2] != 0xa5; 50 | let mut btver = [0u8; 4]; 51 | btver.copy_from_slice(&resp.payload()[14..18]); 52 | 53 | if chip.support_code_flash_protect() 54 | && resp.payload()[2 + 8..2 + 8 + 4] != [0xff, 0xff, 0xff, 0xff] 55 | { 56 | log::warn!( 57 | "WRP register: {}", 58 | hex::encode(&resp.payload()[2 + 8..2 + 8 + 4]) 59 | ); 60 | } 61 | 62 | // NOTE: just read all remain bytes as chip_uid 63 | let chip_uid = resp.payload()[18..].to_vec(); 64 | 65 | let f = Flashing { 66 | transport: Box::new(transport), 67 | chip, 68 | chip_uid, 69 | bootloader_version: btver, 70 | code_flash_protected, 71 | }; 72 | f.check_chip_uid()?; 73 | Ok(f) 74 | } 75 | 76 | pub fn new_from_serial(port: Option<&str>, baudrate: Option) -> Result { 77 | let baudrate = baudrate.unwrap_or_default(); 78 | 79 | let transport = match port { 80 | Some(port) => SerialTransport::open(port, baudrate)?, 81 | None => SerialTransport::open_any(baudrate)?, 82 | }; 83 | 84 | Self::new_from_transport(transport) 85 | } 86 | 87 | pub fn new_from_usb(device: Option) -> Result { 88 | let transport = match device { 89 | Some(device) => UsbTransport::open_nth(device)?, 90 | None => UsbTransport::open_any()?, 91 | }; 92 | 93 | Self::new_from_transport(transport) 94 | } 95 | 96 | /// Reidentify chip using correct chip uid 97 | pub fn reidenfity(&mut self) -> Result<()> { 98 | let identify = Command::identify(self.chip.chip_id, self.chip.device_type); 99 | let resp = self.transport.transfer(identify)?; 100 | 101 | anyhow::ensure!(resp.payload()[0] == self.chip.chip_id, "chip id mismatch"); 102 | anyhow::ensure!( 103 | resp.payload()[1] == self.chip.device_type, 104 | "device type mismatch" 105 | ); 106 | 107 | let read_conf = Command::read_config(CFG_MASK_ALL); 108 | let _ = self.transport.transfer(read_conf)?; 109 | 110 | Ok(()) 111 | } 112 | 113 | pub fn check_chip_name(&self, name: &str) -> Result<()> { 114 | if !self.chip.name.starts_with(name) { 115 | anyhow::bail!( 116 | "chip name mismatch: has {}, provided {}", 117 | self.chip.name, 118 | name 119 | ); 120 | } 121 | Ok(()) 122 | } 123 | 124 | pub fn dump_info(&mut self) -> Result<()> { 125 | if self.chip.eeprom_size > 0 { 126 | if self.chip.eeprom_size % 1024 != 0 { 127 | log::info!( 128 | "Chip: {} (Code Flash: {}KiB, Data EEPROM: {} Bytes)", 129 | self.chip, 130 | self.chip.flash_size / 1024, 131 | self.chip.eeprom_size 132 | ); 133 | } else { 134 | log::info!( 135 | "Chip: {} (Code Flash: {}KiB, Data EEPROM: {}KiB)", 136 | self.chip, 137 | self.chip.flash_size / 1024, 138 | self.chip.eeprom_size / 1024 139 | ); 140 | } 141 | } else { 142 | log::info!( 143 | "Chip: {} (Code Flash: {}KiB)", 144 | self.chip, 145 | self.chip.flash_size / 1024, 146 | ); 147 | } 148 | log::info!( 149 | "Chip UID: {}", 150 | self.chip_uid 151 | .iter() 152 | .map(|x| format!("{:02X}", x)) 153 | .collect::>() 154 | .join("-") 155 | ); 156 | log::info!( 157 | "BTVER(bootloader ver): {:x}{:x}.{:x}{:x}", 158 | self.bootloader_version[0], 159 | self.bootloader_version[1], 160 | self.bootloader_version[2], 161 | self.bootloader_version[3] 162 | ); 163 | 164 | if self.chip.support_code_flash_protect() { 165 | log::info!("Code Flash protected: {}", self.code_flash_protected); 166 | } 167 | self.dump_config()?; 168 | 169 | Ok(()) 170 | } 171 | 172 | /// Unprotect code flash. 173 | pub fn unprotect(&mut self, force: bool) -> Result<()> { 174 | if !force && !self.code_flash_protected { 175 | return Ok(()); 176 | } 177 | let read_conf = Command::read_config(CFG_MASK_RDPR_USER_DATA_WPR); 178 | let resp = self.transport.transfer(read_conf)?; 179 | anyhow::ensure!(resp.is_ok(), "read_config failed"); 180 | 181 | let mut config = resp.payload()[2..14].to_vec(); // 4 x u32 182 | config[0] = 0xa5; // code flash unprotected 183 | config[1] = 0x5a; 184 | 185 | // WPR register 186 | config[8..12].copy_from_slice(&[0xff; 4]); 187 | 188 | let write_conf = Command::write_config(CFG_MASK_RDPR_USER_DATA_WPR, config); 189 | let resp = self.transport.transfer(write_conf)?; 190 | anyhow::ensure!(resp.is_ok(), "write_config failed"); 191 | 192 | log::info!("Code Flash unprotected"); 193 | self.reset()?; 194 | Ok(()) 195 | } 196 | 197 | pub fn reset(&mut self) -> Result<()> { 198 | let isp_end = Command::isp_end(1); 199 | let resp = self.transport.transfer(isp_end)?; 200 | anyhow::ensure!(resp.is_ok(), "isp_end failed"); 201 | 202 | log::info!("Device reset"); 203 | Ok(()) 204 | } 205 | 206 | // unprotect -> erase -> flash -> verify -> reset 207 | /// Program the code flash. 208 | pub fn flash(&mut self, raw: &[u8]) -> Result<()> { 209 | let key = self.xor_key(); 210 | let key_checksum = key.iter().fold(0_u8, |acc, &x| acc.overflowing_add(x).0); 211 | 212 | // NOTE: use all-zero key seed for now. 213 | let isp_key = Command::isp_key(vec![0; 0x1e]); 214 | let resp = self.transport.transfer(isp_key)?; 215 | anyhow::ensure!(resp.is_ok(), "isp_key failed"); 216 | anyhow::ensure!(resp.payload()[0] == key_checksum, "isp_key checksum failed"); 217 | 218 | const CHUNK: usize = 56; 219 | let mut address = 0x0; 220 | 221 | let bar = ProgressBar::new(raw.len() as _); 222 | for ch in raw.chunks(CHUNK) { 223 | self.flash_chunk(address, ch, key)?; 224 | address += ch.len() as u32; 225 | bar.inc(ch.len() as _); 226 | } 227 | // NOTE: require a write action of empty data for success flashing 228 | self.flash_chunk(address, &[], key)?; 229 | bar.finish(); 230 | 231 | log::info!("Code flash {} bytes written", address); 232 | 233 | Ok(()) 234 | } 235 | 236 | pub fn write_eeprom(&mut self, raw: &[u8]) -> Result<()> { 237 | let key = self.xor_key(); 238 | // let key_checksum = key.iter().fold(0_u8, |acc, &x| acc.overflowing_add(x).0); 239 | 240 | // NOTE: use all-zero key seed for now. 241 | let isp_key = Command::isp_key(vec![0; 0x1e]); 242 | let resp = self.transport.transfer(isp_key)?; 243 | anyhow::ensure!(resp.is_ok(), "isp_key failed"); 244 | // anyhow::ensure!(resp.payload()[0] == key_checksum, "isp_key checksum failed"); 245 | 246 | const CHUNK: usize = 56; 247 | let mut address = 0x0; 248 | 249 | let bar = ProgressBar::new(raw.len() as _); 250 | for ch in raw.chunks(CHUNK) { 251 | self.write_data_chunk(address, ch, key)?; 252 | address += ch.len() as u32; 253 | bar.inc(ch.len() as _); 254 | } 255 | // NOTE: require a write action of empty data for success flashing 256 | self.flash_chunk(address, &[], key)?; 257 | bar.finish(); 258 | 259 | Ok(()) 260 | } 261 | 262 | pub fn verify(&mut self, raw: &[u8]) -> Result<()> { 263 | let key = self.xor_key(); 264 | let key_checksum = key.iter().fold(0_u8, |acc, &x| acc.overflowing_add(x).0); 265 | // NOTE: use all-zero key seed for now. 266 | let isp_key = Command::isp_key(vec![0; 0x1e]); 267 | let resp = self.transport.transfer(isp_key)?; 268 | anyhow::ensure!(resp.is_ok(), "isp_key failed"); 269 | anyhow::ensure!(resp.payload()[0] == key_checksum, "isp_key checksum failed"); 270 | 271 | const CHUNK: usize = 56; 272 | let mut address = 0x0; 273 | let bar = ProgressBar::new(raw.len() as _); 274 | for ch in raw.chunks(CHUNK) { 275 | self.verify_chunk(address, ch, key)?; 276 | address += ch.len() as u32; 277 | bar.inc(ch.len() as _); 278 | } 279 | bar.finish(); 280 | 281 | Ok(()) 282 | } 283 | 284 | pub fn reset_config(&mut self) -> Result<()> { 285 | let read_conf = Command::read_config(CFG_MASK_RDPR_USER_DATA_WPR); 286 | let resp = self.transport.transfer(read_conf)?; 287 | anyhow::ensure!(resp.is_ok(), "read_config failed"); 288 | 289 | let mut raw = resp.payload()[2..].to_vec(); 290 | 291 | log::info!("Current config registers: {}", hex::encode(&raw)); 292 | 293 | for reg_desc in &self.chip.config_registers { 294 | if let Some(reset) = reg_desc.reset { 295 | raw.pwrite_with(reset, reg_desc.offset, scroll::LE)?; 296 | } 297 | } 298 | 299 | log::info!("Reset config registers: {}", hex::encode(&raw)); 300 | let write_conf = Command::write_config(CFG_MASK_RDPR_USER_DATA_WPR, raw); 301 | let resp = self.transport.transfer(write_conf)?; 302 | anyhow::ensure!(resp.is_ok(), "write_config failed"); 303 | 304 | // read back 305 | let read_conf = Command::read_config(CFG_MASK_RDPR_USER_DATA_WPR); 306 | let resp = self.transport.transfer(read_conf)?; 307 | anyhow::ensure!(resp.is_ok(), "read_config failed"); 308 | 309 | Ok(()) 310 | } 311 | 312 | pub fn enable_debug(&mut self) -> Result<()> { 313 | let read_conf = Command::read_config(CFG_MASK_RDPR_USER_DATA_WPR); 314 | let resp = self.transport.transfer(read_conf)?; 315 | anyhow::ensure!(resp.is_ok(), "read_config failed"); 316 | 317 | let mut raw = resp.payload()[2..].to_vec(); 318 | 319 | log::info!("Current config registers: {}", hex::encode(&raw)); 320 | 321 | for reg_desc in &self.chip.config_registers { 322 | if let Some(reset) = reg_desc.reset { 323 | raw.pwrite_with(reset, reg_desc.offset, scroll::LE)?; 324 | } 325 | if let Some(enable_debug) = reg_desc.enable_debug { 326 | raw.pwrite_with(enable_debug, reg_desc.offset, scroll::LE)?; 327 | } 328 | } 329 | 330 | log::info!( 331 | "Reset config registers to debug enabled: {}", 332 | hex::encode(&raw) 333 | ); 334 | let write_conf = Command::write_config(CFG_MASK_RDPR_USER_DATA_WPR, raw); 335 | let resp = self.transport.transfer(write_conf)?; 336 | anyhow::ensure!(resp.is_ok(), "write_config failed"); 337 | 338 | // read back 339 | let read_conf = Command::read_config(CFG_MASK_RDPR_USER_DATA_WPR); 340 | let resp = self.transport.transfer(read_conf)?; 341 | anyhow::ensure!(resp.is_ok(), "read_config failed"); 342 | 343 | Ok(()) 344 | } 345 | 346 | /// Dump EEPROM, i.e. data flash. 347 | pub fn dump_eeprom(&mut self) -> Result> { 348 | const CHUNK: usize = 0x3a; 349 | 350 | if self.chip.eeprom_size == 0 { 351 | anyhow::bail!("Chip does not support EEPROM"); 352 | } 353 | let bar = ProgressBar::new(self.chip.eeprom_size as _); 354 | 355 | let mut ret: Vec = Vec::with_capacity(self.chip.eeprom_size as _); 356 | let mut address = 0x0; 357 | while address < self.chip.eeprom_size as u32 { 358 | let chunk_size = u16::min(CHUNK as u16, self.chip.eeprom_size as u16 - address as u16); 359 | 360 | let cmd = Command::data_read(address, chunk_size); 361 | let resp = self.transport.transfer(cmd)?; 362 | anyhow::ensure!(resp.is_ok(), "data_read failed"); 363 | 364 | anyhow::ensure!( 365 | resp.payload()[2..].len() == chunk_size as usize, 366 | "data_read length mismatch" 367 | ); 368 | if resp.payload()[2..] == [0xfe, 0x00] { 369 | anyhow::bail!("EEPROM read failed, required chunk size cannot be satisfied"); 370 | } 371 | ret.extend_from_slice(&resp.payload()[2..]); 372 | address += chunk_size as u32; 373 | 374 | bar.inc(chunk_size as _); 375 | if chunk_size < CHUNK as u16 { 376 | bar.finish(); 377 | break; 378 | } 379 | } 380 | anyhow::ensure!( 381 | ret.len() == self.chip.eeprom_size as _, 382 | "EEPROM size mismatch, expected {}, got {}", 383 | self.chip.eeprom_size, 384 | ret.len() 385 | ); 386 | Ok(ret) 387 | } 388 | 389 | fn flash_chunk(&mut self, address: u32, raw: &[u8], key: [u8; 8]) -> Result<()> { 390 | let xored = raw.iter().enumerate().map(|(i, x)| x ^ key[i % 8]); 391 | let padding = rand::random(); 392 | let cmd = Command::program(address, padding, xored.collect()); 393 | let resp = self 394 | .transport 395 | .transfer_with_wait(cmd, Duration::from_millis(300))?; 396 | anyhow::ensure!(resp.is_ok(), "program 0x{:08x} failed", address); 397 | Ok(()) 398 | } 399 | 400 | fn write_data_chunk(&mut self, address: u32, raw: &[u8], key: [u8; 8]) -> Result<()> { 401 | let xored = raw.iter().enumerate().map(|(i, x)| x ^ key[i % 8]); 402 | let padding = rand::random(); 403 | let cmd = Command::data_program(address, padding, xored.collect()); 404 | // NOTE: EEPROM write might be slow. Use 5ms timeout. 405 | let resp = self 406 | .transport 407 | .transfer_with_wait(cmd, Duration::from_millis(5))?; 408 | anyhow::ensure!(resp.is_ok(), "program data 0x{:08x} failed", address); 409 | Ok(()) 410 | } 411 | 412 | fn verify_chunk(&mut self, address: u32, raw: &[u8], key: [u8; 8]) -> Result<()> { 413 | let xored = raw.iter().enumerate().map(|(i, x)| x ^ key[i % 8]); 414 | let padding = rand::random(); 415 | let cmd = Command::verify(address, padding, xored.collect()); 416 | let resp = self.transport.transfer(cmd)?; 417 | anyhow::ensure!(resp.is_ok(), "verify response failed"); 418 | anyhow::ensure!(resp.payload()[0] == 0x00, "Verify failed, mismatch"); 419 | Ok(()) 420 | } 421 | 422 | pub fn erase_code(&mut self, mut sectors: u32) -> Result<()> { 423 | let min_sectors = self.chip.min_erase_sector_number(); 424 | if sectors < min_sectors { 425 | sectors = min_sectors; 426 | log::warn!( 427 | "erase_code: set min number of erased sectors to {}", 428 | sectors 429 | ); 430 | } 431 | let erase = Command::erase(sectors); 432 | let resp = self 433 | .transport 434 | .transfer_with_wait(erase, Duration::from_millis(5000))?; 435 | anyhow::ensure!(resp.is_ok(), "erase failed"); 436 | 437 | log::info!("Erased {} code flash sectors", sectors); 438 | Ok(()) 439 | } 440 | 441 | pub fn erase_data(&mut self) -> Result<()> { 442 | if self.chip.eeprom_size == 0 { 443 | anyhow::bail!("chip doesn't support data EEPROM"); 444 | } 445 | let sectors = (self.chip.eeprom_size / 1024).max(1) as u16; 446 | let erase = Command::data_erase(sectors as _); 447 | let resp = self 448 | .transport 449 | .transfer_with_wait(erase, Duration::from_millis(1000))?; 450 | anyhow::ensure!(resp.is_ok(), "erase_data failed"); 451 | 452 | log::info!("Erased {} data flash sectors", sectors); 453 | Ok(()) 454 | } 455 | 456 | pub fn dump_config(&mut self) -> Result<()> { 457 | // CH32X03x chips do not support bit mask read 458 | // let read_conf = Command::read_config(CFG_MASK_RDPR_USER_DATA_WPR); 459 | let read_conf = Command::read_config(CFG_MASK_ALL); 460 | let resp = self.transport.transfer(read_conf)?; 461 | anyhow::ensure!(resp.is_ok(), "read_config failed"); 462 | 463 | let raw = &resp.payload()[2..]; 464 | log::info!("Current config registers: {}", hex::encode(&raw)); 465 | 466 | for reg_def in &self.chip.config_registers { 467 | let n = raw.pread_with::(reg_def.offset, LE)?; 468 | println!("{}: 0x{:08X}", reg_def.name, n); 469 | 470 | for (val, expain) in ®_def.explaination { 471 | if val == "_" || Some(n) == parse_number(val) { 472 | println!(" `- {}", expain); 473 | break; 474 | } 475 | } 476 | 477 | // byte fields 478 | for field_def in ®_def.fields { 479 | let bit_width = (field_def.bit_range[0] - field_def.bit_range[1]) as u32 + 1; 480 | let b = (n >> field_def.bit_range[1]) & (2_u32.pow(bit_width) - 1); 481 | println!( 482 | " {:<7} {} 0x{:X} (0b{:b})", 483 | format!("[{:}:{:}]", field_def.bit_range[0], field_def.bit_range[1]), 484 | field_def.name, 485 | b, 486 | b 487 | ); 488 | for (val, expain) in &field_def.explaination { 489 | if val == "_" || Some(b) == parse_number(val) { 490 | println!(" `- {}", expain); 491 | break; 492 | } 493 | } 494 | } 495 | } 496 | 497 | Ok(()) 498 | } 499 | 500 | // NOTE: XOR key for all-zero key seed 501 | fn xor_key(&self) -> [u8; 8] { 502 | let checksum = self 503 | .chip_uid() 504 | .iter() 505 | .fold(0_u8, |acc, &x| acc.overflowing_add(x).0); 506 | let mut key = [checksum; 8]; 507 | key.last_mut() 508 | .map(|x| *x = x.overflowing_add(self.chip.chip_id).0); 509 | key 510 | } 511 | 512 | pub fn chip_uid(&self) -> &[u8] { 513 | let uid_size = self.chip.uid_size(); 514 | //if self.bootloader_version < [0, 2, 4, 0] { 515 | // uid_size = 4 516 | //} 517 | &self.chip_uid[..uid_size] 518 | } 519 | 520 | fn check_chip_uid(&self) -> Result<()> { 521 | if self.chip.uid_size() == 8 { 522 | let raw = self.chip_uid(); 523 | let checked = raw 524 | .pread_with::(0, LE)? 525 | .overflowing_add(raw.pread_with::(2, LE)?) 526 | .0 527 | .overflowing_add(raw.pread_with::(4, LE)?) 528 | .0 529 | == raw.pread_with::(6, LE)?; 530 | anyhow::ensure!(checked, "Chip UID checksum failed!"); 531 | } 532 | Ok(()) 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | //! Firmware file formats 2 | use std::str; 3 | use std::{borrow::Cow, path::Path}; 4 | 5 | use anyhow::Result; 6 | use object::{ 7 | elf::FileHeader32, elf::PT_LOAD, read::elf::FileHeader, read::elf::ProgramHeader, Endianness, 8 | Object, ObjectSection, 9 | }; 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 12 | pub enum FirmwareFormat { 13 | PlainHex, 14 | IntelHex, 15 | ELF, 16 | Binary, 17 | } 18 | 19 | pub fn read_firmware_from_file>(path: P) -> Result> { 20 | let p = path.as_ref(); 21 | let raw = std::fs::read(p)?; 22 | 23 | let format = guess_format(p, &raw); 24 | log::info!("Read {} as {:?} format", p.display(), format); 25 | match format { 26 | FirmwareFormat::PlainHex => Ok(hex::decode( 27 | raw.into_iter() 28 | .filter(|&c| c != b'\r' || c != b'\n') 29 | .collect::>(), 30 | )?), 31 | FirmwareFormat::IntelHex => Ok(read_ihex(str::from_utf8(&raw)?)?), 32 | FirmwareFormat::ELF => Ok(objcopy_binary(&raw)?), 33 | FirmwareFormat::Binary => Ok(raw), 34 | } 35 | } 36 | 37 | pub fn guess_format(path: &Path, raw: &[u8]) -> FirmwareFormat { 38 | let ext = path 39 | .extension() 40 | .map(|s| s.to_string_lossy()) 41 | .unwrap_or_default() 42 | .to_lowercase(); 43 | if ["ihex", "ihe", "h86", "hex", "a43", "a90"].contains(&&*ext) { 44 | return FirmwareFormat::IntelHex; 45 | } 46 | 47 | // FIXME: is this 4-byte possible to be some kind of assembly binary? 48 | if raw.starts_with(&[0x7f, b'E', b'L', b'F']) { 49 | FirmwareFormat::ELF 50 | } else if raw[0] == b':' 51 | && raw 52 | .iter() 53 | .all(|&c| (c as char).is_ascii_hexdigit() || c == b':' || c == b'\n' || c == b'\r') 54 | { 55 | FirmwareFormat::IntelHex 56 | } else if raw 57 | .iter() 58 | .all(|&c| (c as char).is_ascii_hexdigit() || c == b'\n' || c == b'\r') 59 | { 60 | FirmwareFormat::PlainHex 61 | } else { 62 | FirmwareFormat::Binary 63 | } 64 | } 65 | 66 | pub fn read_hex(data: &str) -> Result> { 67 | Ok(hex::decode(data)?) 68 | } 69 | 70 | pub fn read_ihex(data: &str) -> Result> { 71 | use ihex::Record; 72 | 73 | let mut base_address = 0; 74 | 75 | let mut records = vec![]; 76 | for record in ihex::Reader::new(&data) { 77 | let record = record?; 78 | use Record::*; 79 | match record { 80 | Data { offset, value } => { 81 | let offset = base_address + offset as u32; 82 | 83 | records.push((offset, value.into())); 84 | } 85 | EndOfFile => (), 86 | ExtendedSegmentAddress(address) => { 87 | base_address = (address as u32) * 16; 88 | } 89 | StartSegmentAddress { .. } => (), 90 | ExtendedLinearAddress(address) => { 91 | base_address = (address as u32) << 16; 92 | } 93 | StartLinearAddress(_) => (), 94 | }; 95 | } 96 | merge_sections(records) 97 | } 98 | 99 | /// Simulates `objcopy -O binary`. 100 | pub fn objcopy_binary(elf_data: &[u8]) -> Result> { 101 | let file_kind = object::FileKind::parse(elf_data)?; 102 | 103 | match file_kind { 104 | object::FileKind::Elf32 => (), 105 | _ => anyhow::bail!("cannot read file as ELF32 format"), 106 | } 107 | let elf_header = FileHeader32::::parse(elf_data)?; 108 | let binary = object::read::elf::ElfFile::>::parse(elf_data)?; 109 | 110 | let mut sections = vec![]; 111 | 112 | let endian = elf_header.endian()?; 113 | 114 | // Ref: https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-83432/index.html 115 | for segment in elf_header.program_headers(elf_header.endian()?, elf_data)? { 116 | // Get the physical address of the segment. The data will be programmed to that location. 117 | let p_paddr: u64 = segment.p_paddr(endian).into(); 118 | // Virtual address 119 | let p_vaddr: u64 = segment.p_vaddr(endian).into(); 120 | 121 | let flags = segment.p_flags(endian); 122 | 123 | let segment_data = segment 124 | .data(endian, elf_data) 125 | .map_err(|_| anyhow::format_err!("Failed to access data for an ELF segment."))?; 126 | if !segment_data.is_empty() && segment.p_type(endian) == PT_LOAD { 127 | log::info!( 128 | "Found loadable segment, physical address: {:#010x}, virtual address: {:#010x}, flags: {:#x}", 129 | p_paddr, 130 | p_vaddr, 131 | flags 132 | ); 133 | let (segment_offset, segment_filesize) = segment.file_range(endian); 134 | let mut section_names = vec![]; 135 | for section in binary.sections() { 136 | let (section_offset, section_filesize) = match section.file_range() { 137 | Some(range) => range, 138 | None => continue, 139 | }; 140 | if section_filesize == 0 { 141 | continue; 142 | } 143 | 144 | // contains range 145 | if segment_offset <= section_offset 146 | && segment_offset + segment_filesize >= section_offset + section_filesize 147 | { 148 | log::debug!( 149 | "Matching section: {:?} offset: 0x{:x} size: 0x{:x}", 150 | section.name()?, 151 | section_offset, 152 | section_filesize 153 | ); 154 | for (offset, relocation) in section.relocations() { 155 | log::debug!("Relocation: offset={}, relocation={:?}", offset, relocation); 156 | } 157 | section_names.push(section.name()?.to_owned()); 158 | } 159 | } 160 | let section_data = &elf_data[segment_offset as usize..][..segment_filesize as usize]; 161 | sections.push((p_paddr as u32, section_data.into())); 162 | log::info!("Section names: {:?}", section_names); 163 | } 164 | } 165 | 166 | if sections.is_empty() { 167 | anyhow::bail!("empty ELF file"); 168 | } 169 | log::debug!("found {} sections", sections.len()); 170 | merge_sections(sections) 171 | } 172 | 173 | fn merge_sections(mut sections: Vec<(u32, Cow<[u8]>)>) -> Result> { 174 | sections.sort(); // order by start address 175 | 176 | let start_address = sections.first().unwrap().0; 177 | let end_address = sections.last().unwrap().0 + sections.last().unwrap().1.len() as u32; 178 | 179 | let total_size = end_address - start_address; 180 | 181 | let mut binary = vec![0u8; total_size as usize]; 182 | // FIXMME: check section overlap? 183 | for (addr, sect) in sections { 184 | let sect_start = (addr - start_address) as usize; 185 | let sect_end = (addr - start_address) as usize + sect.len(); 186 | binary[sect_start..sect_end].copy_from_slice(§); 187 | } 188 | Ok(binary) 189 | } 190 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! WCH ISP Protocol implementation. 2 | 3 | pub mod constants; 4 | pub mod device; 5 | pub mod flashing; 6 | pub mod format; 7 | pub mod protocol; 8 | pub mod transport; 9 | 10 | pub use self::device::Chip; 11 | pub use self::flashing::Flashing; 12 | pub use self::protocol::{Command, Response}; 13 | pub use self::transport::{Baudrate, Transport}; 14 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{thread::sleep, time::Duration}; 2 | 3 | use anyhow::Result; 4 | 5 | use clap::{Parser, Subcommand}; 6 | use hxdmp::hexdump; 7 | 8 | use wchisp::{ 9 | constants::SECTOR_SIZE, 10 | transport::{SerialTransport, UsbTransport}, 11 | Baudrate, Flashing, 12 | }; 13 | 14 | #[derive(Parser)] 15 | #[command(author, version, about, long_about = None)] 16 | #[clap(group(clap::ArgGroup::new("transport").args(&["usb", "serial"])))] 17 | struct Cli { 18 | /// Turn debugging information on 19 | #[arg(long = "verbose", short = 'v')] 20 | debug: bool, 21 | 22 | /// Use the USB transport layer 23 | #[arg(long, short, default_value_t = true, default_value_if("serial", clap::builder::ArgPredicate::IsPresent, "false"), conflicts_with_all = ["serial", "port", "baudrate"])] 24 | usb: bool, 25 | 26 | /// Use the Serial transport layer 27 | #[arg(long, short, conflicts_with_all = ["usb", "device"])] 28 | serial: bool, 29 | 30 | /// Optional USB device index to operate on 31 | #[arg(long, short, value_name = "INDEX", default_value = None, requires = "usb")] 32 | device: Option, 33 | 34 | /// Select the serial port 35 | #[arg(long, short, requires = "serial")] 36 | port: Option, 37 | 38 | /// Select the serial baudrate 39 | #[arg(long, short, ignore_case = true, value_enum, requires = "serial")] 40 | baudrate: Option, 41 | 42 | #[command(subcommand)] 43 | command: Option, 44 | } 45 | 46 | #[derive(Subcommand)] 47 | enum Commands { 48 | /// Probe any connected devices 49 | Probe {}, 50 | /// Get info about current connected chip 51 | Info { 52 | /// Chip name(prefix) check 53 | #[arg(long)] 54 | chip: Option, 55 | }, 56 | /// Reset the target connected 57 | Reset {}, 58 | /// Erase code flash 59 | Erase {}, 60 | /// Download to code flash and reset 61 | Flash { 62 | /// The path to the file to be downloaded to the code flash 63 | path: String, 64 | /// Do not erase the code flash before flashing 65 | #[clap(short = 'E', long)] 66 | no_erase: bool, 67 | /// Do not verify the code flash after flashing 68 | #[clap(short = 'V', long)] 69 | no_verify: bool, 70 | /// Do not reset the target after flashing 71 | #[clap(short = 'R', long)] 72 | no_reset: bool, 73 | }, 74 | /// Verify code flash content 75 | Verify { path: String }, 76 | /// EEPROM(data flash) operations 77 | Eeprom { 78 | #[command(subcommand)] 79 | command: Option, 80 | }, 81 | /// Config CFG register 82 | Config { 83 | #[command(subcommand)] 84 | command: Option, 85 | }, 86 | } 87 | 88 | #[derive(Subcommand)] 89 | enum ConfigCommands { 90 | /// Dump config register info 91 | Info {}, 92 | /// Reset config register to default 93 | Reset {}, 94 | /// Enable SWD mode(simulation mode) 95 | EnableDebug {}, 96 | /// Set config register to new value 97 | Set { 98 | /// New value of the config register 99 | #[arg(value_name = "HEX")] 100 | value: String, 101 | }, 102 | /// Unprotect code flash 103 | Unprotect {}, 104 | } 105 | 106 | #[derive(Subcommand)] 107 | enum EepromCommands { 108 | /// Dump EEPROM data 109 | Dump { 110 | /// The path of the file to be written to 111 | path: Option, 112 | }, 113 | /// Erase EEPROM data 114 | Erase {}, 115 | /// Programming EEPROM data 116 | Write { 117 | /// The path to the file to be downloaded to the data flash 118 | path: String, 119 | /// Do not erase the data flash before programming 120 | #[clap(short = 'E', long)] 121 | no_erase: bool, 122 | }, 123 | } 124 | 125 | fn main() -> Result<()> { 126 | let cli = Cli::parse(); 127 | 128 | if cli.debug { 129 | let _ = simplelog::TermLogger::init( 130 | simplelog::LevelFilter::Debug, 131 | simplelog::Config::default(), 132 | simplelog::TerminalMode::Mixed, 133 | simplelog::ColorChoice::Auto, 134 | ); 135 | } else { 136 | let _ = simplelog::TermLogger::init( 137 | simplelog::LevelFilter::Info, 138 | simplelog::Config::default(), 139 | simplelog::TerminalMode::Mixed, 140 | simplelog::ColorChoice::Auto, 141 | ); 142 | } 143 | 144 | match &cli.command { 145 | None | Some(Commands::Probe {}) => { 146 | if cli.usb { 147 | let ndevices = UsbTransport::scan_devices()?; 148 | log::info!( 149 | "Found {ndevices} USB device{}", 150 | match ndevices { 151 | 1 => "", 152 | _ => "s", 153 | } 154 | ); 155 | for i in 0..ndevices { 156 | let mut trans = UsbTransport::open_nth(i)?; 157 | let chip = Flashing::get_chip(&mut trans)?; 158 | log::info!("\tDevice #{i}: {chip}"); 159 | } 160 | } 161 | if cli.serial { 162 | let ports = SerialTransport::scan_ports()?; 163 | let port_len = ports.len(); 164 | log::info!( 165 | "Found {port_len} serial port{}:", 166 | match port_len { 167 | 1 => "", 168 | _ => "s", 169 | } 170 | ); 171 | for p in ports { 172 | log::info!("\t{p}"); 173 | } 174 | } 175 | 176 | log::info!("hint: use `wchisp info` to check chip info"); 177 | } 178 | Some(Commands::Info { chip }) => { 179 | let mut flashing = get_flashing(&cli)?; 180 | 181 | if let Some(expected_chip_name) = chip { 182 | flashing.check_chip_name(&expected_chip_name)?; 183 | } 184 | flashing.dump_info()?; 185 | } 186 | Some(Commands::Reset {}) => { 187 | let mut flashing = get_flashing(&cli)?; 188 | 189 | let _ = flashing.reset(); 190 | } 191 | Some(Commands::Erase {}) => { 192 | let mut flashing = get_flashing(&cli)?; 193 | 194 | let sectors = flashing.chip.flash_size / 1024; 195 | flashing.erase_code(sectors)?; 196 | } 197 | // WRITE_CONFIG => READ_CONFIG => ISP_KEY => ERASE => PROGRAM => VERIFY => RESET 198 | Some(Commands::Flash { 199 | path, 200 | no_erase, 201 | no_verify, 202 | no_reset, 203 | }) => { 204 | let mut flashing = get_flashing(&cli)?; 205 | 206 | flashing.dump_info()?; 207 | 208 | let mut binary = wchisp::format::read_firmware_from_file(path)?; 209 | extend_firmware_to_sector_boundary(&mut binary); 210 | log::info!("Firmware size: {}", binary.len()); 211 | 212 | if *no_erase { 213 | log::warn!("Skipping erase"); 214 | } else { 215 | log::info!("Erasing..."); 216 | let sectors = binary.len() / SECTOR_SIZE + 1; 217 | flashing.erase_code(sectors as u32)?; 218 | 219 | sleep(Duration::from_secs(1)); 220 | log::info!("Erase done"); 221 | } 222 | 223 | log::info!("Writing to code flash..."); 224 | flashing.flash(&binary)?; 225 | sleep(Duration::from_millis(500)); 226 | 227 | if *no_verify { 228 | log::warn!("Skipping verify"); 229 | } else { 230 | log::info!("Verifying..."); 231 | flashing.verify(&binary)?; 232 | log::info!("Verify OK"); 233 | } 234 | 235 | if *no_reset { 236 | log::warn!("Skipping reset"); 237 | } else { 238 | log::info!("Now reset device and skip any communication errors"); 239 | let _ = flashing.reset(); 240 | } 241 | } 242 | Some(Commands::Verify { path }) => { 243 | let mut flashing = get_flashing(&cli)?; 244 | 245 | let mut binary = wchisp::format::read_firmware_from_file(path)?; 246 | extend_firmware_to_sector_boundary(&mut binary); 247 | log::info!("Firmware size: {}", binary.len()); 248 | log::info!("Verifying..."); 249 | flashing.verify(&binary)?; 250 | log::info!("Verify OK"); 251 | } 252 | Some(Commands::Eeprom { command }) => { 253 | let mut flashing = get_flashing(&cli)?; 254 | 255 | match command { 256 | None | Some(EepromCommands::Dump { .. }) => { 257 | flashing.reidenfity()?; 258 | 259 | log::info!("Reading EEPROM(Data Flash)..."); 260 | 261 | let eeprom = flashing.dump_eeprom()?; 262 | log::info!("EEPROM data size: {}", eeprom.len()); 263 | 264 | if let Some(EepromCommands::Dump { 265 | path: Some(ref path), 266 | }) = command 267 | { 268 | std::fs::write(path, eeprom)?; 269 | log::info!("EEPROM data saved to {}", path); 270 | } else { 271 | let mut buf = vec![]; 272 | hexdump(&eeprom, &mut buf)?; 273 | println!("{}", String::from_utf8_lossy(&buf)); 274 | } 275 | } 276 | Some(EepromCommands::Erase {}) => { 277 | flashing.reidenfity()?; 278 | 279 | log::info!("Erasing EEPROM(Data Flash)..."); 280 | flashing.erase_data()?; 281 | log::info!("EEPROM erased"); 282 | } 283 | Some(EepromCommands::Write { path, no_erase }) => { 284 | flashing.reidenfity()?; 285 | 286 | if *no_erase { 287 | log::warn!("Skipping erase"); 288 | } else { 289 | log::info!("Erasing EEPROM(Data Flash)..."); 290 | flashing.erase_data()?; 291 | log::info!("EEPROM erased"); 292 | } 293 | 294 | let eeprom = std::fs::read(path)?; 295 | log::info!("Read {} bytes from bin file", eeprom.len()); 296 | if eeprom.len() as u32 != flashing.chip.eeprom_size { 297 | anyhow::bail!( 298 | "EEPROM size mismatch: expected {}, got {}", 299 | flashing.chip.eeprom_size, 300 | eeprom.len() 301 | ); 302 | } 303 | 304 | log::info!("Writing EEPROM(Data Flash)..."); 305 | flashing.write_eeprom(&eeprom)?; 306 | log::info!("EEPROM written"); 307 | } 308 | } 309 | } 310 | Some(Commands::Config { command }) => { 311 | let mut flashing = get_flashing(&cli)?; 312 | 313 | match command { 314 | None | Some(ConfigCommands::Info {}) => { 315 | flashing.dump_config()?; 316 | } 317 | Some(ConfigCommands::Reset {}) => { 318 | flashing.reset_config()?; 319 | log::info!( 320 | "Config register restored to default value(non-protected, debug-enabled)" 321 | ); 322 | } 323 | Some(ConfigCommands::EnableDebug {}) => { 324 | flashing.enable_debug()?; 325 | log::info!("Debug mode enabled"); 326 | } 327 | Some(ConfigCommands::Set { value }) => { 328 | // flashing.write_config(value)?; 329 | log::info!("setting cfg value {}", value); 330 | unimplemented!() 331 | } 332 | Some(ConfigCommands::Unprotect {}) => { 333 | flashing.unprotect(true)?; 334 | } 335 | } 336 | } 337 | } 338 | 339 | Ok(()) 340 | } 341 | 342 | fn extend_firmware_to_sector_boundary(buf: &mut Vec) { 343 | if buf.len() % 1024 != 0 { 344 | let remain = 1024 - (buf.len() % 1024); 345 | buf.extend_from_slice(&vec![0; remain]); 346 | } 347 | } 348 | 349 | fn get_flashing(cli: &Cli) -> Result> { 350 | if cli.usb { 351 | Flashing::new_from_usb(cli.device) 352 | } else if cli.serial { 353 | Flashing::new_from_serial(cli.port.as_deref(), cli.baudrate) 354 | } else { 355 | unreachable!("No transport specified"); 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/protocol.rs: -------------------------------------------------------------------------------- 1 | //! The underlying binary protocol of WCH ISP 2 | 3 | use std::fmt; 4 | 5 | use anyhow::Result; 6 | use scroll::{Pread, Pwrite}; 7 | 8 | use crate::constants::commands; 9 | 10 | /// WCH ISP Command 11 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | pub enum Command { 13 | /// Identify the MCU. 14 | /// Return the real `device_id`, `device_type`. 15 | /// 16 | /// DeviceType = ChipSeries = SerialNumber = McuType + 0x10 17 | Identify { device_id: u8, device_type: u8 }, 18 | /// End ISP session, reboot the device. 19 | /// 20 | /// Connection will lost after response packet 21 | IspEnd { 22 | reason: u8, // 0 for normal, 1 for config set 23 | }, 24 | /// Send ISP key seed to MCU. 25 | /// Return checksum of the XOR key(1 byte sum). 26 | /// 27 | /// The detailedd key algrithm: 28 | /// 29 | /// - sum Device UID to a byte, s 30 | /// - initialize XOR key as [s; 8] 31 | /// - select 7 bytes(via some rules) from generated random key 32 | /// - `key[0] ~ key[6] ^= corresponding selected byte` 33 | /// - `key[7] = key[0] + chip_id` 34 | /// 35 | /// In many open source implementations, the key is initialized as [0; N], 36 | /// which makes it easier to do the calculation 37 | IspKey { key: Vec }, 38 | /// Erase the Code Flash. 39 | /// 40 | /// Minmum sectors is either 8 or 4 depends on device type. 41 | Erase { sectors: u32 }, 42 | /// Program the Code Flash. 43 | /// 44 | /// `data` is xored with the XOR key. 45 | /// `padding` is a random byte(Looks like a checksum, but it's not) 46 | Program { 47 | address: u32, 48 | padding: u8, 49 | data: Vec, 50 | }, 51 | /// Verify the Code Flash, almost the same as `Program` 52 | Verify { 53 | address: u32, 54 | padding: u8, 55 | data: Vec, 56 | }, 57 | /// Read Config Bits. 58 | ReadConfig { bit_mask: u8 }, 59 | /// Write Config Bits. Can be used to unprotect the device. 60 | WriteConfig { bit_mask: u8, data: Vec }, 61 | /// Erase the Data Flash, almost the same as `Erase` 62 | DataErase { sectors: u32 }, 63 | /// Program the Data Flash, almost the same as `Program` 64 | DataProgram { 65 | address: u32, 66 | padding: u8, 67 | data: Vec, 68 | }, 69 | /// Read the Data Flash 70 | DataRead { address: u32, len: u16 }, 71 | /// Write OTP 72 | WriteOTP(u8), 73 | /// Read OTP 74 | ReadOTP(u8), 75 | /// Set baudrate 76 | SetBaud { baudrate: u32 }, 77 | } 78 | 79 | impl Command { 80 | pub fn identify(device_id: u8, device_type: u8) -> Self { 81 | Command::Identify { 82 | device_id, 83 | device_type, 84 | } 85 | } 86 | 87 | pub fn isp_end(reason: u8) -> Self { 88 | Command::IspEnd { reason } 89 | } 90 | 91 | pub fn isp_key(key: Vec) -> Self { 92 | Command::IspKey { key } 93 | } 94 | 95 | pub fn read_config(bit_mask: u8) -> Self { 96 | Command::ReadConfig { bit_mask } 97 | } 98 | 99 | pub fn write_config(bit_mask: u8, data: Vec) -> Self { 100 | Command::WriteConfig { bit_mask, data } 101 | } 102 | 103 | pub fn erase(sectors: u32) -> Self { 104 | Command::Erase { sectors } 105 | } 106 | 107 | pub fn program(address: u32, padding: u8, data: Vec) -> Self { 108 | Command::Program { 109 | address, 110 | padding, 111 | data, 112 | } 113 | } 114 | 115 | pub fn verify(address: u32, padding: u8, data: Vec) -> Self { 116 | Command::Verify { 117 | address, 118 | padding, 119 | data, 120 | } 121 | } 122 | 123 | // 0x3a per packet 124 | pub fn data_read(address: u32, len: u16) -> Self { 125 | Command::DataRead { address, len } 126 | } 127 | 128 | pub fn data_program(address: u32, padding: u8, data: Vec) -> Self { 129 | Command::DataProgram { 130 | address, 131 | padding, 132 | data, 133 | } 134 | } 135 | 136 | pub fn data_erase(sectors: u32) -> Self { 137 | Command::DataErase { sectors } 138 | } 139 | 140 | pub fn set_baud(baudrate: u32) -> Self { 141 | Command::SetBaud { baudrate } 142 | } 143 | 144 | // TODO(visiblity) 145 | pub fn into_raw(self) -> Result> { 146 | match self { 147 | Command::Identify { 148 | device_id, 149 | device_type, 150 | } => { 151 | let mut buf = Vec::with_capacity(0x12 + 3); 152 | buf.push(commands::IDENTIFY); 153 | buf.extend_from_slice(&[0x12, 0]); 154 | buf.push(device_id); 155 | buf.push(device_type); 156 | buf.extend_from_slice(b"MCU ISP & WCH.CN"); 157 | Ok(buf) 158 | } 159 | Command::IspEnd { reason } => Ok([commands::ISP_END, 0x01, 00, reason].to_vec()), 160 | Command::IspKey { key } => { 161 | let mut buf = Vec::with_capacity(3 + key.len()); 162 | buf.push(commands::ISP_KEY); 163 | buf.push(key.len() as u8); 164 | buf.push(0x00); 165 | buf.extend(key); 166 | Ok(buf) 167 | } 168 | // a4 169 | // 04 00 170 | // 08 00 00 00 171 | Command::Erase { sectors } => { 172 | let mut buf = [commands::ERASE, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00]; 173 | buf.pwrite_with(sectors, 3, scroll::LE)?; 174 | Ok(buf.to_vec()) 175 | } 176 | Command::Program { 177 | address, 178 | padding, 179 | data, 180 | } => { 181 | // CMD, SIZE, ADDR, PADDING, DATA 182 | let mut buf = vec![0u8; 1 + 2 + 4 + 1 + data.len()]; 183 | buf[0] = commands::PROGRAM; 184 | buf.pwrite_with(address, 3, scroll::LE)?; 185 | buf[7] = padding; 186 | buf[8..].copy_from_slice(&data); 187 | let payload_size = buf.len() as u16 - 3; 188 | buf.pwrite_with(payload_size, 1, scroll::LE)?; 189 | Ok(buf) 190 | } 191 | Command::Verify { 192 | address, 193 | padding, 194 | data, 195 | } => { 196 | let mut buf = vec![0u8; 1 + 2 + 4 + 1 + data.len()]; 197 | buf[0] = commands::VERIFY; 198 | buf.pwrite_with(address, 3, scroll::LE)?; 199 | buf[7] = padding; 200 | buf[8..].copy_from_slice(&data); 201 | let payload_size = buf.len() as u16 - 3; 202 | buf.pwrite_with(payload_size, 1, scroll::LE)?; 203 | Ok(buf) 204 | } 205 | Command::ReadConfig { bit_mask } => { 206 | let buf = [commands::READ_CONFIG, 0x02, 0x00, bit_mask, 0x00]; 207 | Ok(buf.to_vec()) 208 | } 209 | Command::WriteConfig { bit_mask, data } => { 210 | let mut buf = vec![0u8; 1 + 2 + 2 + data.len()]; 211 | buf[0] = commands::WRITE_CONFIG; 212 | buf.pwrite_with(2 + data.len() as u16, 1, scroll::LE)?; 213 | buf[3] = bit_mask; 214 | buf[5..].copy_from_slice(&data); 215 | Ok(buf) 216 | } 217 | Command::DataRead { address, len } => { 218 | let mut buf = [0u8; 9]; 219 | buf[0] = commands::DATA_READ; 220 | buf[1] = 6; // fixed len 221 | 222 | buf.pwrite_with(address, 3, scroll::LE)?; 223 | buf.pwrite_with(len, 7, scroll::LE)?; 224 | Ok(buf.to_vec()) 225 | } 226 | // aa command 227 | // 3d 00 length 228 | // 38 00 00 00 address 229 | // 1c padding 230 | // .... payload, using 8-byte key to encrypt 231 | Command::DataProgram { 232 | address, 233 | padding, 234 | data, 235 | } => { 236 | let mut buf = vec![0u8; 1 + 2 + 4 + 1 + data.len()]; 237 | buf[0] = commands::DATA_PROGRAM; 238 | buf.pwrite_with(address, 3, scroll::LE)?; 239 | buf[7] = padding; 240 | buf[8..].copy_from_slice(&data); 241 | let payload_size = buf.len() as u16 - 3; 242 | buf.pwrite_with(payload_size, 1, scroll::LE)?; 243 | Ok(buf) 244 | } 245 | // a9 246 | // 05 00 247 | // 00 00 00 00 ??? 248 | // 20 sectors of data flash 249 | Command::DataErase { sectors } => { 250 | let mut buf = [ 251 | commands::DATA_ERASE, 252 | 0x05, 253 | 0x00, 254 | 0x00, 255 | 0x00, 256 | 0x00, 257 | 0x00, 258 | 0x00, 259 | ]; 260 | // FIXME: is this correct? 261 | buf[7] = sectors as u8; 262 | Ok(buf.to_vec()) 263 | } 264 | Command::SetBaud { baudrate } => { 265 | let baudrate = baudrate.to_le_bytes(); 266 | let buf = vec![ 267 | commands::SET_BAUD, 268 | 0x04, 269 | 0x00, 270 | baudrate[0], 271 | baudrate[1], 272 | baudrate[2], 273 | baudrate[3], 274 | ]; 275 | Ok(buf) 276 | } 277 | // TODO: WriteOTP, ReadOTP 278 | _ => unimplemented!(), 279 | } 280 | } 281 | } 282 | 283 | /// Response to a Command. The request cmd type is ommitted from the type definition. 284 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 285 | pub enum Response { 286 | /// Code = 0x00 287 | Ok(Vec), 288 | /// Otherwise 289 | Err(u8, Vec), 290 | } 291 | 292 | impl fmt::Debug for Response { 293 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 294 | match self { 295 | Response::Ok(data) => write!(f, "OK[{}]", hex::encode(data)), 296 | Response::Err(code, data) => write!(f, "ERROR({:x})[{}]", code, hex::encode(data)), 297 | } 298 | } 299 | } 300 | 301 | impl Response { 302 | pub fn is_ok(&self) -> bool { 303 | match self { 304 | Response::Ok(_) => true, 305 | _ => false, 306 | } 307 | } 308 | 309 | pub fn payload(&self) -> &[u8] { 310 | match self { 311 | Response::Ok(payload) => payload, 312 | Response::Err(_, payload) => payload, 313 | } 314 | } 315 | 316 | pub(crate) fn from_raw(raw: &[u8]) -> Result { 317 | // FIXME: should raw[1] == 0x00 || raw[1] == 0x82? 318 | if true { 319 | let len = raw.pread_with::(2, scroll::LE)? as usize; 320 | let remain = &raw[4..]; 321 | if remain.len() == len { 322 | Ok(Response::Ok(remain.to_vec())) 323 | } else { 324 | Err(anyhow::anyhow!("Invalid response")) 325 | } 326 | } else { 327 | Ok(Response::Err(raw[1], raw[2..].to_vec())) 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | //! Abstract Device transport interface. 2 | use std::{thread::sleep, time::Duration}; 3 | 4 | use anyhow::Result; 5 | 6 | use crate::protocol::{Command, Response}; 7 | 8 | pub use self::serial::{Baudrate, SerialTransport}; 9 | pub use self::usb::UsbTransport; 10 | 11 | mod serial; 12 | mod usb; 13 | 14 | const DEFAULT_TRANSPORT_TIMEOUT_MS: u64 = 1000; 15 | 16 | /// Abstraction of the transport layer. 17 | /// Might be a USB, a serial port, or Network. 18 | pub trait Transport { 19 | fn send_raw(&mut self, raw: &[u8]) -> Result<()>; 20 | fn recv_raw(&mut self, timeout: Duration) -> Result>; 21 | 22 | fn transfer(&mut self, cmd: Command) -> Result { 23 | self.transfer_with_wait(cmd, Duration::from_millis(DEFAULT_TRANSPORT_TIMEOUT_MS)) 24 | } 25 | 26 | fn transfer_with_wait(&mut self, cmd: Command, wait: Duration) -> Result { 27 | let req = &cmd.into_raw()?; 28 | log::debug!("=> {} {}", hex::encode(&req[..3]), hex::encode(&req[3..])); 29 | self.send_raw(&req)?; 30 | sleep(Duration::from_micros(1)); // required for some Linux platform 31 | 32 | let resp = self.recv_raw(wait)?; 33 | anyhow::ensure!(req[0] == resp[0], "response command type mismatch"); 34 | log::debug!("<= {} {}", hex::encode(&resp[..4]), hex::encode(&resp[4..])); 35 | Response::from_raw(&resp) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/transport/serial.rs: -------------------------------------------------------------------------------- 1 | //! Serial Transportation. 2 | use std::{fmt::Display, io::Read, time::Duration}; 3 | 4 | use anyhow::{Error, Ok, Result}; 5 | use clap::{builder::PossibleValue, ValueEnum}; 6 | use scroll::Pread; 7 | use serialport::SerialPort; 8 | 9 | use super::{Command, Transport}; 10 | 11 | const SERIAL_TIMEOUT_MS: u64 = 1000; 12 | 13 | #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] 14 | pub enum Baudrate { 15 | #[default] 16 | Baud115200, 17 | Baud1m, 18 | Baud2m, 19 | } 20 | 21 | impl From for u32 { 22 | fn from(value: Baudrate) -> Self { 23 | match value { 24 | Baudrate::Baud115200 => 115200, 25 | Baudrate::Baud1m => 1000000, 26 | Baudrate::Baud2m => 2000000, 27 | } 28 | } 29 | } 30 | 31 | impl Display for Baudrate { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | write!(f, "{}", u32::from(*self)) 34 | } 35 | } 36 | 37 | impl ValueEnum for Baudrate { 38 | fn value_variants<'a>() -> &'a [Self] { 39 | &[Baudrate::Baud115200, Baudrate::Baud1m, Baudrate::Baud2m] 40 | } 41 | 42 | fn to_possible_value(&self) -> Option { 43 | match self { 44 | Baudrate::Baud115200 => Some(PossibleValue::new("Baud115200").aliases(["115200"])), 45 | Baudrate::Baud1m => { 46 | Some(PossibleValue::new("Baud1m").aliases(["1000000", "1_000_000", "1m"])) 47 | } 48 | Baudrate::Baud2m => { 49 | Some(PossibleValue::new("Baud2m").aliases(["2000000", "2_000_000", "2m"])) 50 | } 51 | } 52 | } 53 | } 54 | 55 | pub struct SerialTransport { 56 | serial_port: Box, 57 | } 58 | 59 | impl SerialTransport { 60 | pub fn scan_ports() -> Result> { 61 | let ports = serialport::available_ports()?; 62 | Ok(ports.into_iter().map(|p| p.port_name).collect()) 63 | } 64 | 65 | pub fn open(port: &str, baudrate: Baudrate) -> Result { 66 | log::info!("Opening serial port: \"{}\" @ 115200 baud", port); 67 | let port = serialport::new(port, Baudrate::default().into()) 68 | .timeout(Duration::from_millis(SERIAL_TIMEOUT_MS)) 69 | .open()?; 70 | 71 | let mut transport = SerialTransport { serial_port: port }; 72 | transport.set_baudrate(baudrate)?; 73 | 74 | Ok(transport) 75 | } 76 | 77 | pub fn open_nth(nth: usize, baudrate: Baudrate) -> Result { 78 | let ports = serialport::available_ports()?; 79 | 80 | match ports.get(nth) { 81 | Some(port) => Self::open(&port.port_name, baudrate), 82 | None => Err(Error::msg("No serial ports found!")), 83 | } 84 | } 85 | 86 | pub fn open_any(baudrate: Baudrate) -> Result { 87 | Self::open_nth(0, baudrate) 88 | } 89 | 90 | pub fn set_baudrate(&mut self, baudrate: impl Into) -> Result<()> { 91 | let baudrate: u32 = baudrate.into(); 92 | 93 | if baudrate != self.serial_port.baud_rate()? { 94 | let resp: crate::Response = self.transfer(Command::set_baud(baudrate))?; 95 | anyhow::ensure!(resp.is_ok(), "set baudrate failed"); 96 | 97 | if let Some(0xfe) = resp.payload().first() { 98 | log::info!("Custom baudrate not supported by the current chip. Using 115200"); 99 | } else { 100 | log::info!("Switching baudrate to: {baudrate} baud"); 101 | self.serial_port.set_baud_rate(baudrate.into())?; 102 | } 103 | } 104 | 105 | Ok(()) 106 | } 107 | } 108 | 109 | impl Transport for SerialTransport { 110 | fn send_raw(&mut self, raw: &[u8]) -> Result<()> { 111 | let mut v = Vec::new(); 112 | 113 | v.extend_from_slice(&[0x57, 0xab]); // Append request prefix 114 | v.extend_from_slice(raw); 115 | v.extend_from_slice(&[raw.iter().fold(0u8, |acc, &val| acc.wrapping_add(val))]); // Append the CRC 116 | 117 | self.serial_port.write_all(&v)?; 118 | self.serial_port.flush()?; 119 | Ok(()) 120 | } 121 | 122 | fn recv_raw(&mut self, _timeout: Duration) -> Result> { 123 | // Ignore the custom timeout 124 | // self.serial_port.set_timeout(timeout)?; 125 | 126 | // Read the serial header and validate. 127 | let mut head_buf = [0u8; 2]; 128 | self.serial_port.read_exact(&mut head_buf)?; 129 | anyhow::ensure!( 130 | head_buf == [0x55, 0xaa], 131 | "Response has invalid serial header {head_buf:02x?}", 132 | ); 133 | 134 | // Read the payload header and extract given length value. 135 | let mut payload_head_buf = [0u8; 4]; 136 | self.serial_port.read_exact(&mut payload_head_buf)?; 137 | let payload_data_len = payload_head_buf.pread_with::(2, scroll::LE)? as usize; 138 | anyhow::ensure!(payload_data_len > 0, "Response data length is zero"); 139 | 140 | // Read the amount of payload data given in the header. 141 | let mut payload_data_buf = vec![0u8; payload_data_len]; 142 | self.serial_port.read_exact(&mut payload_data_buf)?; 143 | 144 | // Read the checksum and verify against actual sum calculated from 145 | // entire payload (header + data). 146 | let mut cksum_buf = [0u8; 1]; 147 | self.serial_port.read_exact(&mut cksum_buf)?; 148 | 149 | // Stuff the payload header and data into response to be returned. 150 | let resp_vec: Vec = payload_head_buf 151 | .into_iter() 152 | .chain(payload_data_buf.into_iter()) 153 | .collect(); 154 | 155 | // Read the checksum and verify against actual sum calculated from 156 | // entire payload (header + data). 157 | let checksum = resp_vec.iter().fold(0u8, |acc, &val| acc.wrapping_add(val)); 158 | anyhow::ensure!( 159 | checksum == cksum_buf[0], 160 | "Response has incorrect checksum ({:02x} != {:02x})", 161 | cksum_buf[0], 162 | checksum 163 | ); 164 | 165 | Ok(resp_vec) 166 | } 167 | } 168 | 169 | impl Drop for SerialTransport { 170 | fn drop(&mut self) { 171 | let _ = self.set_baudrate(Baudrate::Baud115200); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/transport/usb.rs: -------------------------------------------------------------------------------- 1 | //! USB Transportation. 2 | use std::time::Duration; 3 | 4 | use anyhow::Result; 5 | use rusb::{Context, DeviceHandle, UsbContext}; 6 | 7 | use super::Transport; 8 | 9 | const ENDPOINT_OUT: u8 = 0x02; 10 | const ENDPOINT_IN: u8 = 0x82; 11 | 12 | const USB_TIMEOUT_MS: u64 = 5000; 13 | 14 | pub struct UsbTransport { 15 | device_handle: DeviceHandle, 16 | } 17 | 18 | impl UsbTransport { 19 | pub fn scan_devices() -> Result { 20 | let context = Context::new()?; 21 | 22 | let n = context 23 | .devices()? 24 | .iter() 25 | .filter(|device| { 26 | device 27 | .device_descriptor() 28 | .map(|desc| { 29 | (desc.vendor_id() == 0x4348 || desc.vendor_id() == 0x1a86) 30 | && desc.product_id() == 0x55e0 31 | }) 32 | .unwrap_or(false) 33 | }) 34 | .enumerate() 35 | .map(|(i, device)| { 36 | log::debug!("Found WCH ISP USB device #{}: [{:?}]", i, device); 37 | }) 38 | .count(); 39 | Ok(n) 40 | } 41 | 42 | pub fn open_nth(nth: usize) -> Result { 43 | log::info!("Opening USB device #{}", nth); 44 | 45 | let context = Context::new()?; 46 | 47 | let device = context 48 | .devices()? 49 | .iter() 50 | .filter(|device| { 51 | device 52 | .device_descriptor() 53 | .map(|desc| { 54 | (desc.vendor_id() == 0x4348 || desc.vendor_id() == 0x1a86) 55 | && desc.product_id() == 0x55e0 56 | }) 57 | .unwrap_or(false) 58 | }) 59 | .nth(nth) 60 | .ok_or(anyhow::format_err!( 61 | "No WCH ISP USB device found(4348:55e0 or 1a86:55e0 device not found at index #{})", 62 | nth 63 | ))?; 64 | log::debug!("Found USB Device {:?}", device); 65 | 66 | let device_handle = match device.open() { 67 | Ok(handle) => handle, 68 | #[cfg(target_os = "windows")] 69 | Err(rusb::Error::NotSupported) => { 70 | log::error!("Failed to open USB device: {:?}", device); 71 | log::warn!("It's likely no WinUSB/LibUSB drivers installed. Please install it from Zadig. See also: https://zadig.akeo.ie"); 72 | anyhow::bail!("Failed to open USB device on Windows"); 73 | } 74 | #[cfg(target_os = "linux")] 75 | Err(rusb::Error::Access) => { 76 | log::error!("Failed to open USB device: {:?}", device); 77 | log::warn!("It's likely the udev rules is not installed properly. Please refer to README.md for more details."); 78 | anyhow::bail!("Failed to open USB device on Linux due to no enough permission"); 79 | } 80 | Err(e) => { 81 | log::error!("Failed to open USB device: {}", e); 82 | anyhow::bail!("Failed to open USB device"); 83 | } 84 | }; 85 | 86 | let config = device.config_descriptor(0)?; 87 | 88 | let mut endpoint_out_found = false; 89 | let mut endpoint_in_found = false; 90 | if let Some(intf) = config.interfaces().next() { 91 | if let Some(desc) = intf.descriptors().next() { 92 | for endpoint in desc.endpoint_descriptors() { 93 | if endpoint.address() == ENDPOINT_OUT { 94 | endpoint_out_found = true; 95 | } 96 | if endpoint.address() == ENDPOINT_IN { 97 | endpoint_in_found = true; 98 | } 99 | } 100 | } 101 | } 102 | 103 | if !(endpoint_out_found && endpoint_in_found) { 104 | anyhow::bail!("USB Endpoints not found"); 105 | } 106 | 107 | device_handle.set_active_configuration(1)?; 108 | let _config = device.active_config_descriptor()?; 109 | let _descriptor = device.device_descriptor()?; 110 | 111 | device_handle.claim_interface(0)?; 112 | 113 | Ok(UsbTransport { device_handle }) 114 | } 115 | 116 | pub fn open_any() -> Result { 117 | Self::open_nth(0) 118 | } 119 | } 120 | 121 | impl Drop for UsbTransport { 122 | fn drop(&mut self) { 123 | // ignore any communication error 124 | let _ = self.device_handle.release_interface(0); 125 | // self.device_handle.reset().unwrap(); 126 | } 127 | } 128 | 129 | impl Transport for UsbTransport { 130 | fn send_raw(&mut self, raw: &[u8]) -> Result<()> { 131 | self.device_handle 132 | .write_bulk(ENDPOINT_OUT, raw, Duration::from_millis(USB_TIMEOUT_MS))?; 133 | Ok(()) 134 | } 135 | 136 | fn recv_raw(&mut self, timeout: Duration) -> Result> { 137 | let mut buf = [0u8; 64]; 138 | let nread = self 139 | .device_handle 140 | .read_bulk(ENDPOINT_IN, &mut buf, timeout)?; 141 | Ok(buf[..nread].to_vec()) 142 | } 143 | } 144 | --------------------------------------------------------------------------------