├── .clang-format ├── .github ├── dependabot.yml └── workflows │ ├── ccpp.yml │ └── release.yml ├── .gitignore ├── .hgignore ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── client.py ├── pico_sdk_import.cmake ├── raspberrypi-swd.cfg └── src ├── globals.h ├── stdio-queue.cpp ├── sws.pio ├── telinkdebugger.cpp ├── tusb_config.h ├── usb-descriptors.cpp └── usb-uart.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: DontAlign 4 | AlignArrayOfStructures: Left 5 | AlignEscapedNewlines: Left 6 | AllowAllArgumentsOnNextLine: 'true' 7 | AllowAllConstructorInitializersOnNextLine: 'false' 8 | AllowAllParametersOfDeclarationOnNextLine: 'true' 9 | AllowShortBlocksOnASingleLine: 'true' 10 | AllowShortCaseLabelsOnASingleLine: 'false' 11 | AllowShortFunctionsOnASingleLine: Empty 12 | AllowShortIfStatementsOnASingleLine: Never 13 | AllowShortLambdasOnASingleLine: None 14 | AllowShortLoopsOnASingleLine: 'false' 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: 'true' 18 | AlwaysBreakTemplateDeclarations: 'Yes' 19 | BinPackArguments: 'false' 20 | BinPackParameters: 'false' 21 | BreakBeforeBraces: Allman 22 | BreakConstructorInitializers: 'AfterColon' 23 | BreakInheritanceList: AfterColon 24 | BreakStringLiterals: 'true' 25 | ColumnLimit: '80' 26 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 27 | FixNamespaceComments: 'false' 28 | IncludeBlocks: Preserve 29 | IndentCaseLabels: 'true' 30 | IndentWidth: '4' 31 | IndentWrappedFunctionNames: 'false' 32 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 33 | NamespaceIndentation: All 34 | PointerAlignment: Left 35 | ReflowComments: 'true' 36 | SortIncludes: 'false' 37 | SortUsingDeclarations: 'true' 38 | SpaceAfterTemplateKeyword: 'true' 39 | SpaceBeforeAssignmentOperators: 'true' 40 | SpaceBeforeCtorInitializerColon: 'false' 41 | SpaceBeforeInheritanceColon: 'true' 42 | SpaceBeforeParens: ControlStatements 43 | SpaceInEmptyParentheses: 'false' 44 | ... 45 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | target-branch: "main" 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ci-${{ github.head_ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | build-linux: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: apt 15 | run: sudo apt update && sudo apt install build-essential cmake gcc-arm-none-eabi 16 | - name: make 17 | run: | 18 | cmake -S . -B build 19 | make -C build -j $(nprocs) 20 | 21 | - name: upload 22 | uses: actions/upload-artifact@v4 23 | with: 24 | name: ${{ github.event.repository.name }}.${{ github.sha }} 25 | path: build/telinkdebugger.uf2 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Autorelease 2 | 3 | concurrency: 4 | group: autorelease-${{ github.head_ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - "master" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | release-linux: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: apt 19 | run: sudo apt update && sudo apt install build-essential cmake gcc-arm-none-eabi 20 | - name: make 21 | run: | 22 | cmake -S . -B build 23 | make -C build -j $(nprocs) 24 | 25 | - name: date 26 | run: | 27 | echo "RELEASE_DATE=$(date --rfc-3339=date)" >> ${GITHUB_ENV} 28 | 29 | - name: tag 30 | uses: EndBug/latest-tag@latest 31 | with: 32 | ref: dev 33 | force-branch: false 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: delete-old-assets 38 | uses: mknejp/delete-release-assets@v1 39 | with: 40 | token: ${{ github.token }} 41 | tag: dev 42 | fail-if-no-assets: false 43 | assets: | 44 | telinkdebugger.uf2 45 | 46 | - name: release 47 | uses: softprops/action-gh-release@v2 48 | with: 49 | name: Development build ${{ env.RELEASE_DATE }} 50 | files: | 51 | build/telinkdebugger.uf2 52 | tag_name: dev 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | build/ 3 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generated Cmake Pico project file 2 | 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 8 | set(CMAKE_BUILD_TYPE Debug) 9 | 10 | set(PICO_SDK_FETCH_FROM_GIT ON) 11 | set(USERHOME $ENV{HOME}) 12 | set(PICO_BOARD pico CACHE STRING "Board type") 13 | 14 | # Pull in Raspberry Pi Pico SDK (must be before project) 15 | include(pico_sdk_import.cmake) 16 | 17 | if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") 18 | message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") 19 | endif() 20 | 21 | project(telinkdebugger C CXX ASM) 22 | 23 | # Initialise the Raspberry Pi Pico SDK 24 | pico_sdk_init() 25 | 26 | add_executable(telinkdebugger 27 | src/telinkdebugger.cpp 28 | src/usb-descriptors.cpp 29 | src/usb-uart.cpp 30 | src/stdio-queue.cpp 31 | ) 32 | 33 | pico_set_program_name(telinkdebugger "telinkdebugger") 34 | pico_set_program_version(telinkdebugger "0.1") 35 | 36 | pico_generate_pio_header(telinkdebugger ${CMAKE_CURRENT_LIST_DIR}/src/sws.pio) 37 | 38 | target_include_directories(telinkdebugger PRIVATE 39 | ${CMAKE_CURRENT_LIST_DIR}/src 40 | ${CMAKE_CURRENT_LIST_DIR} # for our common lwipopts or any other standard includes, if required 41 | ) 42 | 43 | target_include_directories(telinkdebugger PRIVATE 44 | pico-sdk/lib/tinyusb/src 45 | ) 46 | 47 | target_link_libraries(telinkdebugger 48 | hardware_flash 49 | hardware_pio 50 | pico_multicore 51 | pico_stdlib 52 | tinyusb_device 53 | ) 54 | 55 | pico_add_extra_outputs(telinkdebugger) 56 | 57 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT license 2 | 3 | Copyright 2024 David Given 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telink USB debugger bridge 2 | 3 | ## What? 4 | 5 | This is a poorly written and hacked together Raspberry Pi Pico-based debugger 6 | bridge for the Telink SWS protocol. It will allow flashing of Telink-based 7 | devices via USB, so far only tested on the incredibly cheap LT716 fitness bands 8 | using the Telink TLSR8232 chip. 9 | 10 | It was put together from information pulled from these sources: 11 | 12 | - [Reverse engineering the M6 smart fitness 13 | bracelet](https://rbaron.net/blog/2021/07/06/Reverse-engineering-the-M6-smart-fitness-band.html) 14 | by rbaron 15 | - [TlsrTools](https://github.com/pvvx/TlsrTools) by pvvx@github 16 | 17 | It would theoretically be possible to extend this into a full machine-code 18 | debugger, but the relevant part of the TLSR8232 register map isn't documented 19 | and it would require reverse engineering and I just don't have the energy. 20 | Please get in touch if you're interested. 21 | 22 | ## How? 23 | 24 | You can either get a precompiled binary from [the Github releases 25 | page](https://github.com/davidgiven/telinkdebugger/releases/tag/dev) or build it 26 | yourself. It's a standard Raspberry Pi Pico project, so you should just be able 27 | to run the cmakefile and it'll build. Flash a normal Pico with the resulting 28 | file (no wireless necessary). 29 | 30 | Then, connect the Pico to your Telink device as follows: 31 | 32 | - Telink RX -> Pico pin 1 (GPIO0) 33 | - Telink TX -> Pico pin 2 (GPIO1) 34 | - Telink SWS -> Pico pin 4 (GPIO2) 35 | - Telink RST -> Pico pin 5 (GPIO3) 36 | 37 | No extra components are needed. Just wire it up directly. 38 | 39 | When the Pico starts up, it'll put the Telink device into reset and expose two 40 | CDC serial ports via USB. The first is the control port which is used to 41 | communicate with the debugger. The second is a standard USB UART interface and 42 | is connected to the TX and RX pins. 43 | 44 | There is a Python script provided for communicating with the debugger: 45 | 46 | ``` 47 | $ ./client.py --serial-port=/dev/ttyACM1 get_soc_id 48 | SOC ID: 0x5316 49 | ``` 50 | 51 | The serial port provided should be that of the control port. 52 | 53 | Useful commands include: 54 | 55 | - `writeb
` --- writes a single byte to RAM 56 | - `dump_ram
[]` --- produces a hex dump of RAM at a given 57 | address. (Note that RAM addresses are always 16-bit. You can't read flash with 58 | this.) 59 | - `read_ram [
] []` --- reads a portion of or all 60 | RAM. 61 | - `read_flash [
] []` --- reads a portion of or all 62 | the flash. This is very slow. 63 | - `write_flash [
] []` --- erases and then writes to 64 | the flash. Only the pages needed are erased. Note that the radio calibration 65 | values are set in the factory and stored in flash at 0x77000. If you are ever 66 | going to want to use Bluetooth, don't overwrite this. 67 | - `run` --- takes the device out of reset. 68 | 69 | There are others. They may or may not work. 70 | 71 | In addition, the control protocol is faintly intended to be human readable --- 72 | connect to it and type a `?` and you'll get a very brief list of commands. 73 | 74 | ## Why not? 75 | 76 | This has only been tested on a TLSR8232 and there will inevitably be problems on 77 | other devices. On the other hand, this will make finding bugs very easy! 78 | 79 | ## Who? 80 | 81 | All the code here has been bodged together from multiple sources, but the main 82 | author of the main bit of code is me, David Given . I have a 83 | website at http://cowlark.com. There may or may not be anything interesting 84 | there. 85 | 86 | ## License 87 | 88 | Everything here is, as far as I know, either public domain or redistributable 89 | under the terms of the MIT license. See the [LICENSE.md](LICENSE.md) file for 90 | the full text. For more information about the authors look at the header of each 91 | file. 92 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | import serial 4 | from tqdm import tqdm 5 | import sys 6 | import os 7 | 8 | serial_port = None 9 | 10 | FLASH_SECTOR_SIZE = 4096 11 | 12 | 13 | def readchar(): 14 | while True: 15 | c = serial_port.read() 16 | if c == b"#": 17 | while c != b"\n": 18 | c = serial_port.read() 19 | 20 | if c not in [b"\n", b"\r", b" "]: 21 | return c 22 | 23 | 24 | def readhex(): 25 | h = bytearray() 26 | while True: 27 | c = readchar() 28 | if c == b"E": 29 | raise BaseException("Protocol error") 30 | if c == b"S": 31 | break 32 | h.append(c[0]) 33 | return bytes.fromhex(str(h, "ascii")) 34 | 35 | 36 | def connect(): 37 | serial_port.write(b"i") 38 | c = readchar() 39 | if c != b"S": 40 | raise BaseException("Connection failed") 41 | 42 | 43 | def run(): 44 | serial_port.write(b"g") 45 | 46 | 47 | def read_bytes_from_target(addr, len): 48 | s = b"R%04x%04x" % (addr, len) 49 | serial_port.write(s) 50 | return readhex() 51 | 52 | 53 | def read_byte_from_target(addr): 54 | return int.from_bytes(read_bytes_from_target(addr, 1)) 55 | 56 | 57 | def read_word_from_target(addr): 58 | return int.from_bytes(read_bytes_from_target(addr, 2), byteorder="little") 59 | 60 | 61 | def read_quad_from_target(addr): 62 | return int.from_bytes(read_bytes_from_target(addr, 4), byteorder="little") 63 | 64 | 65 | def write_bytes_to_target(addr, bytes): 66 | serial_port.write(b"W%04x%04x" % (addr, len(bytes))) 67 | for b in bytes: 68 | serial_port.write(b"%02x" % b) 69 | readhex() 70 | 71 | 72 | def write_byte_to_target(addr, byte): 73 | write_bytes_to_target(addr, byte.to_bytes(1, "little")) 74 | 75 | 76 | def write_word_to_target(addr, word): 77 | write_bytes_to_target(addr, word.to_bytes(2, "little")) 78 | 79 | 80 | def write_quad_to_target(addr, quad): 81 | write_bytes_to_target(addr, quad.to_bytes(4, "little")) 82 | 83 | 84 | def read_flash_status(): 85 | write_byte_to_target(0x0D, 0x00) # flash CS enable 86 | write_byte_to_target(0x0C, 0x03) # read flash command 87 | 88 | write_byte_to_target(0x0C, 0xFF) 89 | byte = read_byte_from_target(0x0C) 90 | 91 | write_byte_to_target(0x0D, 0x01) # flash CS disable 92 | 93 | return byte 94 | 95 | 96 | def read_flash_block(addr, len): 97 | write_byte_to_target(0x0D, 0x00) # flash CS enable 98 | write_byte_to_target(0x0C, 0x03) # read flash command 99 | write_byte_to_target(0x0C, (addr >> 16) & 0xFF) 100 | write_byte_to_target(0x0C, (addr >> 8) & 0xFF) 101 | write_byte_to_target(0x0C, addr & 0xFF) 102 | 103 | write_byte_to_target(0xB3, 0x80) # SWS to FIFO mode 104 | 105 | data = bytearray() 106 | for i in range(0, len): 107 | write_byte_to_target(0x0C, 0xFF) 108 | data.append(read_byte_from_target(0x0C)) 109 | 110 | write_byte_to_target(0xB3, 0x00) # SWS to RAM mode 111 | write_byte_to_target(0x0D, 0x01) # flash CS disable 112 | 113 | return data 114 | 115 | 116 | def wait_for_flash_chip(): 117 | while True: 118 | write_byte_to_target(0x0D, 0x00) # flash CS enable 119 | write_byte_to_target(0x0C, 0x05) # read_status_command 120 | write_byte_to_target(0x0C, 0xFF) # dummy 121 | s = read_byte_from_target(0x0C) 122 | write_byte_to_target(0x0D, 0x01) # flash CS disable 123 | 124 | if s & 0x20: 125 | raise BaseException("flash write failed") 126 | if not (s & 0x01): 127 | break 128 | 129 | 130 | def erase_flash_sector(addr): 131 | write_byte_to_target(0x0D, 0x00) # flash CS enable 132 | write_byte_to_target(0x0C, 0x06) # write enable command 133 | write_byte_to_target(0x0D, 0x01) # flash CS disable 134 | 135 | write_byte_to_target(0x0D, 0x00) # flash CS enable 136 | write_byte_to_target(0x0C, 0x20) # erase flash sector command 137 | write_byte_to_target(0x0C, (addr >> 16) & 0xFF) 138 | write_byte_to_target(0x0C, (addr >> 8) & 0xFF) 139 | write_byte_to_target(0x0C, addr & 0xFF) 140 | write_byte_to_target(0x0D, 0x01) # flash CS disable 141 | 142 | wait_for_flash_chip() 143 | 144 | 145 | def write_flash_block(addr, block): 146 | write_byte_to_target(0x0D, 0x00) # flash CS enable 147 | write_byte_to_target(0x0C, 0x06) # write enable command 148 | write_byte_to_target(0x0D, 0x01) # flash CS disable 149 | 150 | write_byte_to_target(0x0D, 0x00) # flash CS enable 151 | write_byte_to_target(0x0C, 0x02) # write flash command 152 | write_byte_to_target(0x0C, (addr >> 16) & 0xFF) 153 | write_byte_to_target(0x0C, (addr >> 8) & 0xFF) 154 | write_byte_to_target(0x0C, addr & 0xFF) 155 | 156 | for b in block: 157 | write_byte_to_target(0x0C, b) 158 | 159 | write_byte_to_target(0x0D, 0x01) # flash CS disable 160 | 161 | wait_for_flash_chip() 162 | 163 | 164 | def hexdump(bytes, address): 165 | a = address & ~15 166 | end = address + len(bytes) 167 | while True: 168 | if (a & 15) == 0: 169 | sys.stdout.write("%08x : " % a) 170 | ascii = "" 171 | 172 | if (a >= address) and (a < end): 173 | b = bytes[a - address] 174 | sys.stdout.write("%02x " % b) 175 | if (b >= 32) and (b <= 126): 176 | ascii += chr(b) 177 | else: 178 | ascii += "." 179 | else: 180 | sys.stdout.write(" ") 181 | ascii += " " 182 | 183 | if (a & 15) == 15: 184 | sys.stdout.write(": |%s|\n" % ascii) 185 | if a >= end: 186 | return 187 | 188 | a += 1 189 | 190 | 191 | def dump_ram_main(args): 192 | connect() 193 | b = read_bytes_from_target(args.address, args.length) 194 | hexdump(b, args.address) 195 | 196 | 197 | def get_soc_id_main(args): 198 | connect() 199 | b = read_word_from_target(0x007E) 200 | print("SOC ID: 0x%04x\n" % b) 201 | 202 | 203 | def flash_status_main(arg): 204 | connect() 205 | b = read_flash_status() 206 | print("Flash status byte: 0x%02x\n" % b) 207 | 208 | 209 | def read_ram_main(args): 210 | connect() 211 | print( 212 | "Reading RAM from 0x%04x-0x%04x into '%s':" 213 | % (args.address, args.address + args.length, args.filename) 214 | ) 215 | with open(args.filename, "wb") as file: 216 | for base in tqdm( 217 | iterable=range(args.address, args.address + args.length, 1024), 218 | unit_scale=1024, 219 | unit="B", 220 | ): 221 | b = read_bytes_from_target(base, 1024) 222 | file.write(b) 223 | 224 | 225 | def read_flash_main(args): 226 | connect() 227 | print( 228 | "Reading flash from 0x%08x-0x%08x into '%s':" 229 | % (args.address, args.address + args.length, args.filename) 230 | ) 231 | with open(args.filename, "wb") as file: 232 | for base in tqdm( 233 | iterable=range(args.address, args.address + args.length, 1024), 234 | unit_scale=1024, 235 | unit="B", 236 | ): 237 | b = read_flash_block(base, 1024) 238 | file.write(b) 239 | 240 | 241 | def do_erase_flash(args): 242 | print( 243 | "Erasing flash from 0x%08x-0x%08x:" 244 | % (args.address, args.address + args.length) 245 | ) 246 | for base in tqdm( 247 | iterable=range( 248 | args.address, args.address + args.length, FLASH_SECTOR_SIZE 249 | ), 250 | unit_scale=FLASH_SECTOR_SIZE, 251 | unit="B", 252 | ): 253 | erase_flash_sector(base) 254 | 255 | 256 | def erase_flash_main(args): 257 | connect() 258 | do_erase_flash() 259 | 260 | 261 | def write_flash_main(args): 262 | connect() 263 | with open(args.filename, "rb") as file: 264 | file.seek(0, os.SEEK_END) 265 | args.length = min(args.length, file.tell()) 266 | file.seek(0, os.SEEK_SET) 267 | do_erase_flash(args) 268 | print( 269 | "Writing flash from 0x%08x-0x%08x from '%s':" 270 | % (args.address, args.address + args.length, args.filename) 271 | ) 272 | for base in tqdm( 273 | iterable=range(args.address, args.address + args.length, 256), 274 | unit_scale=256, 275 | unit="B", 276 | ): 277 | b = file.read(256) 278 | write_flash_block(base, b) 279 | 280 | 281 | def writeb_main(args): 282 | print("Writing 0x%02x to 0x%04x" % (args.value, args.address)) 283 | write_byte_to_target(args.address, args.value) 284 | 285 | def run_main(args): 286 | run() 287 | 288 | 289 | def main(): 290 | args_parser = argparse.ArgumentParser(description="Telink debugger client") 291 | args_parser.add_argument("--serial-port", type=str, required=True) 292 | subparsers = args_parser.add_subparsers(dest="cmd", required=True) 293 | 294 | dump_ram_parser = subparsers.add_parser("dump_ram") 295 | dump_ram_parser.set_defaults(func=dump_ram_main) 296 | dump_ram_parser.add_argument("address", type=lambda x: int(x, 0)) 297 | dump_ram_parser.add_argument( 298 | "length", nargs="?", default=0x100, type=lambda x: int(x, 0) 299 | ) 300 | 301 | read_ram_parser = subparsers.add_parser("read_ram") 302 | read_ram_parser.set_defaults(func=read_ram_main) 303 | read_ram_parser.add_argument("filename", type=str) 304 | read_ram_parser.add_argument( 305 | "address", nargs="?", default=0, type=lambda x: int(x, 0) 306 | ) 307 | read_ram_parser.add_argument( 308 | "length", nargs="?", default=0xC000, type=lambda x: int(x, 0) 309 | ) 310 | 311 | flash_status_parser = subparsers.add_parser("flash_status") 312 | flash_status_parser.set_defaults(func=flash_status_main) 313 | 314 | read_flash_parser = subparsers.add_parser("read_flash") 315 | read_flash_parser.set_defaults(func=read_flash_main) 316 | read_flash_parser.add_argument("filename", type=str) 317 | read_flash_parser.add_argument( 318 | "address", nargs="?", default=0, type=lambda x: int(x, 0) 319 | ) 320 | read_flash_parser.add_argument( 321 | "length", nargs="?", default=0x7D000, type=lambda x: int(x, 0) 322 | ) 323 | 324 | write_flash_parser = subparsers.add_parser("write_flash") 325 | write_flash_parser.set_defaults(func=write_flash_main) 326 | write_flash_parser.add_argument("filename", type=str) 327 | write_flash_parser.add_argument( 328 | "address", nargs="?", default=0, type=lambda x: int(x, 0) 329 | ) 330 | write_flash_parser.add_argument( 331 | "length", nargs="?", default=0x7D000, type=lambda x: int(x, 0) 332 | ) 333 | 334 | erase_flash_parser = subparsers.add_parser("erase_flash") 335 | erase_flash_parser.set_defaults(func=erase_flash_main) 336 | erase_flash_parser.add_argument( 337 | "address", nargs="?", default=0, type=lambda x: int(x, 0) 338 | ) 339 | erase_flash_parser.add_argument( 340 | "length", nargs="?", default=0x7D000, type=lambda x: int(x, 0) 341 | ) 342 | 343 | get_soc_id_parser = subparsers.add_parser("get_soc_id") 344 | get_soc_id_parser.set_defaults(func=get_soc_id_main) 345 | 346 | run_parser = subparsers.add_parser("run") 347 | run_parser.set_defaults(func=run_main) 348 | 349 | writeb_parser = subparsers.add_parser("writeb") 350 | writeb_parser.set_defaults(func=writeb_main) 351 | writeb_parser.add_argument("address", type=lambda x: int(x, 0)) 352 | writeb_parser.add_argument("value", type=lambda x: int(x, 0)) 353 | 354 | args = args_parser.parse_args() 355 | 356 | global serial_port 357 | serial_port = serial.Serial( 358 | args.serial_port, 359 | 115200, 360 | serial.EIGHTBITS, 361 | serial.PARITY_NONE, 362 | serial.STOPBITS_ONE, 363 | ) 364 | 365 | args.func(args) 366 | 367 | 368 | if __name__ == "__main__": 369 | main() 370 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /raspberrypi-swd.cfg: -------------------------------------------------------------------------------- 1 | proc read_file { name } { 2 | if {[catch {open $name r} fd]} { 3 | return "" 4 | } 5 | set result [read $fd] 6 | close $fd 7 | return $result 8 | } 9 | 10 | set compat [read_file /proc/device-tree/compatible] 11 | 12 | if {[string match *bcm2712* $compat]} { 13 | adapter driver linuxgpiod 14 | 15 | adapter gpio swdio -chip 4 24 16 | adapter gpio swclk -chip 4 25 17 | } else { 18 | source [find interface/raspberrypi-native.cfg] 19 | 20 | adapter gpio swdio -chip 0 24 21 | adapter gpio swclk -chip 0 25 22 | 23 | adapter speed 5000 24 | } 25 | -------------------------------------------------------------------------------- /src/globals.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pico/util/queue.h" 4 | 5 | extern void usb_bridge_init(void); 6 | extern void stdio_queue_init(void); 7 | 8 | extern queue_t rd_queue; 9 | extern queue_t wr_queue; 10 | -------------------------------------------------------------------------------- /src/stdio-queue.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright (c) 2024 David Given 4 | */ 5 | 6 | #include 7 | #include "globals.h" 8 | #include "pico/stdio/driver.h" 9 | 10 | static void stdio_queue_out_chars(const char* buf, int length) 11 | { 12 | for (int i = 0; i < length; i++) 13 | queue_add_blocking(&wr_queue, &buf[i]); 14 | } 15 | 16 | static int stdio_queue_in_chars(char* buf, int length) 17 | { 18 | int i = 0; 19 | while (i < length && !queue_is_empty(&rd_queue)) 20 | queue_remove_blocking(&rd_queue, &buf[i++]); 21 | return i ? i : PICO_ERROR_NO_DATA; 22 | } 23 | 24 | static stdio_driver_t queue_driver = {.out_chars = stdio_queue_out_chars, 25 | .in_chars = stdio_queue_in_chars, 26 | #if PICO_STDIO_ENABLE_CRLF_SUPPORT 27 | .crlf_enabled = true 28 | #endif 29 | }; 30 | 31 | void stdio_queue_init() 32 | { 33 | stdio_set_driver_enabled(&queue_driver, true); 34 | } 35 | -------------------------------------------------------------------------------- /src/sws.pio: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: MIT 2 | ; 3 | ; Copyright (c) 2024 David Given 4 | 5 | .program sws_tx 6 | .side_set 1 opt 7 | ; Sends nine bits of data. 8 | 9 | set pindirs, 0 ; idle with the transmitter off 10 | irq 0 ; signal completion 11 | pull block ; block until data shows up 12 | out null, 23 ; align data left 13 | set y, 8 ; -1 because the test is at the end 14 | 15 | set pindirs, 1 ; turn the transmitter on 16 | set pins, 1 ; ...and high 17 | 18 | ; Four cycles per bit 19 | loop: 20 | out x, 1 side 0 ; 0 21 | jmp !x emit_zero [2] ; 1..3 22 | emit_one: 23 | nop [3+4] ; low 2+3 24 | nop [3] ; low 4 25 | nop [3-1] side 1 ; high, minus one bit for the jump 26 | jmp y-- loop ; go for next bit 27 | jmp end side 0 ; begin terminator 28 | 29 | emit_zero: 30 | nop [3+4] side 1 ; high 1 + 2 31 | nop [3+4-1] ; high 3 + 4, minus one bit for the jump 32 | jmp y-- loop ; go for next bit 33 | nop side 0 ; begin terminator 34 | end: 35 | nop [2] ; send a one bit to terminate 36 | nop [3+4] side 1 ; high 1 + 2 37 | nop [3+4] ; high 3 + 4 38 | .wrap ; and go again 39 | 40 | % c-sdk { 41 | void sws_tx_program_init(PIO pio, uint sm, uint offset, uint pin, double clock_hz) { 42 | pio_sm_config c = sws_tx_program_get_default_config(offset); 43 | sm_config_set_out_shift(&c, /* shift_right= */ false, /* autopull= */ false, /* pull_threshold= */ 32); 44 | sm_config_set_set_pins(&c, pin, 1); 45 | sm_config_set_sideset_pins(&c, pin); 46 | 47 | double sysclock_hz = clock_get_hz(clk_sys); 48 | sm_config_set_clkdiv(&c, sysclock_hz / clock_hz); 49 | 50 | pio_sm_init(pio, sm, offset, &c); 51 | } 52 | %} 53 | 54 | .program sws_rx 55 | .side_set 1 opt 56 | ; Make a clean low period to trigger transmission. 57 | 58 | set pins, 0 59 | set pindirs, 1 side 0 ; transmitter on, low 60 | set x, 31 61 | delay_loop: 62 | jmp x-- delay_loop [7] 63 | set pins, 1 [1] ; bring the output high to avoid a long rise from the weak pullups 64 | set pindirs, 0 ; transmitter off 65 | wait 1 pin 0 ; just to be safe 66 | 67 | set y, 7 ; deliberately n-1 68 | bit_loop: 69 | wait 0 pin 0 70 | 71 | mov x, !null 72 | zero_loop: 73 | jmp pin end_zero_loop 74 | jmp x-- zero_loop [1] 75 | end_zero_loop: 76 | 77 | mov x, !x ; approximately negate 78 | 79 | one_loop: 80 | jmp pin still_one 81 | jmp low_longer_than_high 82 | still_one: 83 | jmp x-- one_loop 84 | high_longer_than_low: 85 | in null, 1 86 | jmp end_loop 87 | 88 | low_longer_than_high: 89 | set x, 1 90 | in x, 1 91 | end_loop: 92 | 93 | jmp y-- bit_loop 94 | 95 | wait 1 pin 0 ; wait until the device stops sending 96 | 97 | set x, 31 ; and then a bit more 98 | delay_loop2: 99 | jmp x-- delay_loop2 [7] 100 | 101 | push block 102 | 103 | end: 104 | jmp end 105 | 106 | % c-sdk { 107 | void sws_rx_program_init(PIO pio, uint sm, uint offset, uint pin) { 108 | pio_sm_config c = sws_rx_program_get_default_config(offset); 109 | sm_config_set_in_shift(&c, /* shift_right= */ false, /* autopush= */ false, /* push_threshold= */ 32); 110 | sm_config_set_in_pins(&c, pin); 111 | sm_config_set_set_pins(&c, pin, 1); 112 | sm_config_set_jmp_pin(&c, pin); 113 | sm_config_set_sideset_pins(&c, pin); 114 | 115 | pio_sm_init(pio, sm, offset, &c); 116 | } 117 | %} 118 | -------------------------------------------------------------------------------- /src/telinkdebugger.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright (c) 2024 David Given 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "pico/stdlib.h" 10 | #include "hardware/pio.h" 11 | #include "hardware/clocks.h" 12 | #include "hardware/divider.h" 13 | 14 | #include "sws.pio.h" 15 | #include "globals.h" 16 | 17 | #define SWS_PIN 2 18 | #define RST_PIN 3 19 | #define DBG_PIN 4 20 | #define LED_PIN PICO_DEFAULT_LED_PIN 21 | 22 | #define SM_RX 0 23 | #define SM_TX 1 24 | 25 | #define BUFFER_SIZE_BITS 4096 26 | 27 | #define REG_ADDR8(n) (n) 28 | #define REG_ADDR16(n) (n) 29 | #define REG_ADDR32(n) (n) 30 | 31 | #define reg_soc_id REG_ADDR16(0x7e) 32 | 33 | #define reg_swire_data REG_ADDR8(0xb0) 34 | #define reg_swire_ctrl1 REG_ADDR8(0xb1) 35 | #define reg_swire_clk_div REG_ADDR8(0xb2) 36 | #define reg_swire_id REG_ADDR8(0xb3) 37 | 38 | #define reg_tmr_ctl REG_ADDR32(0x620) 39 | #define FLD_TMR_WD_EN (1 << 23) 40 | 41 | #define reg_debug_runstate REG_ADDR8(0x602) 42 | 43 | static uint32_t input_buffer[BUFFER_SIZE_BITS / 8]; 44 | static uint32_t output_buffer[BUFFER_SIZE_BITS / 8]; 45 | 46 | static int input_buffer_bit_ptr; 47 | static int output_buffer_bit_ptr; 48 | 49 | static int sws_tx_program_offset; 50 | static int sws_rx_program_offset; 51 | 52 | static bool is_connected; 53 | 54 | static void write_nine_bit_byte(uint16_t byte) 55 | { 56 | pio_gpio_init(pio0, SWS_PIN); 57 | pio_interrupt_clear(pio0, 0); 58 | 59 | pio_sm_put(pio0, SM_TX, byte); 60 | 61 | while (!pio_interrupt_get(pio0, 0)) 62 | ; 63 | pio_interrupt_clear(pio0, 0); 64 | } 65 | 66 | static void write_cmd_byte(uint8_t byte) 67 | { 68 | write_nine_bit_byte(0x100 | byte); 69 | } 70 | 71 | static void write_data_byte(uint8_t byte) 72 | { 73 | write_nine_bit_byte(0x000 | byte); 74 | } 75 | 76 | static void write_data_word(uint16_t word) 77 | { 78 | write_data_byte(word >> 8); 79 | write_data_byte(word & 0xff); 80 | } 81 | 82 | static uint8_t read_byte() 83 | { 84 | pio_gpio_init(pio1, SWS_PIN); 85 | pio_gpio_init(pio1, DBG_PIN); 86 | pio_sm_clear_fifos(pio1, SM_RX); 87 | pio_sm_exec_wait_blocking(pio1, SM_RX, sws_rx_program_offset); // JMP offset 88 | 89 | return pio_sm_get_blocking(pio1, SM_RX); 90 | } 91 | 92 | static uint8_t read_first_debug_byte(uint16_t address) 93 | { 94 | write_cmd_byte(0x5a); 95 | write_data_word(address); 96 | write_data_byte(0x80); 97 | 98 | return read_byte(); 99 | } 100 | 101 | static uint8_t read_next_debug_byte() 102 | { 103 | return read_byte(); 104 | } 105 | 106 | static void finish_reading_debug_bytes() 107 | { 108 | write_cmd_byte(0xff); 109 | } 110 | 111 | static uint8_t read_single_debug_byte(uint16_t address) 112 | { 113 | uint8_t value = read_first_debug_byte(address); 114 | finish_reading_debug_bytes(); 115 | return value; 116 | } 117 | 118 | static uint16_t read_single_debug_word(uint16_t address) 119 | { 120 | uint8_t v1 = read_first_debug_byte(address); 121 | uint8_t v2 = read_next_debug_byte(); 122 | finish_reading_debug_bytes(); 123 | return v1 | (v2 << 8); 124 | } 125 | 126 | static void write_first_debug_byte(uint16_t address, uint8_t value) 127 | { 128 | write_cmd_byte(0x5a); 129 | write_data_word(address); 130 | write_data_byte(0x00); 131 | write_data_byte(value); 132 | } 133 | 134 | static void write_next_debug_byte(uint8_t value) 135 | { 136 | write_data_byte(value); 137 | } 138 | 139 | static void finish_writing_debug_bytes() 140 | { 141 | write_cmd_byte(0xff); 142 | } 143 | 144 | static void write_single_debug_byte(uint16_t address, uint8_t value) 145 | { 146 | write_first_debug_byte(address, value); 147 | finish_writing_debug_bytes(); 148 | } 149 | 150 | static void write_single_debug_word(uint16_t address, uint16_t value) 151 | { 152 | write_first_debug_byte(address, value); 153 | write_next_debug_byte(value >> 8); 154 | finish_writing_debug_bytes(); 155 | } 156 | 157 | static void write_single_debug_quad(uint16_t address, uint32_t value) 158 | { 159 | write_first_debug_byte(address, value); 160 | write_next_debug_byte(value >> 8); 161 | write_next_debug_byte(value >> 16); 162 | write_next_debug_byte(value >> 24); 163 | finish_writing_debug_bytes(); 164 | } 165 | 166 | static void halt_target() 167 | { 168 | write_single_debug_byte(reg_debug_runstate, 0x05); 169 | } 170 | 171 | static void set_target_clock_speed(uint8_t speed) 172 | { 173 | write_single_debug_byte(reg_swire_clk_div, speed); 174 | } 175 | 176 | static void banner() 177 | { 178 | printf( 179 | "# Telink debugger bridge\n" 180 | "# Commands:\n" 181 | "# i verify connection to device\n" 182 | "# rX X=[0, 1] set status of reset pin\n" 183 | "# g take device out of reset\n" 184 | "# s read device socid\n" 185 | "# RXXXXYYYY read YYYY bytes from XXXX (values in hex)\n" 186 | "# WXXXXYYYY... write YYYY bytes to XXXX, folowed by hex pairs\n" 187 | "# Responses are S for success, E for error, and # is a comment.\n" 188 | "# Good luck (you'll need it).\n"); 189 | } 190 | 191 | static void init_cmd() 192 | { 193 | printf("# init\n"); 194 | 195 | gpio_put(RST_PIN, false); 196 | sleep_ms(20); 197 | gpio_put(RST_PIN, true); 198 | sleep_ms(20); 199 | 200 | halt_target(); 201 | 202 | uint16_t socid = read_single_debug_word(reg_soc_id); 203 | if (socid == 0x5316) 204 | { 205 | printf("S\n"); 206 | is_connected = true; 207 | 208 | /* Disable the watchdog timer. */ 209 | 210 | write_single_debug_quad(reg_tmr_ctl, 0); 211 | return; 212 | } 213 | 214 | printf("E\n# init failed\n"); 215 | } 216 | 217 | static uint8_t read_hex_byte() 218 | { 219 | char buffer[3]; 220 | buffer[0] = getchar(); 221 | buffer[1] = getchar(); 222 | buffer[2] = 0; 223 | return strtoul(buffer, nullptr, 16); 224 | } 225 | 226 | static uint16_t read_hex_word() 227 | { 228 | uint8_t hi = read_hex_byte(); 229 | uint8_t lo = read_hex_byte(); 230 | return lo | (hi << 8); 231 | } 232 | 233 | void set_tx_clock(double clock_hz) 234 | { 235 | sws_tx_program_init(pio0, SM_TX, sws_tx_program_offset, SWS_PIN, clock_hz); 236 | pio_sm_set_enabled(pio0, SM_TX, true); 237 | } 238 | 239 | int main(void) 240 | { 241 | usb_bridge_init(); 242 | stdio_queue_init(); 243 | 244 | gpio_init(RST_PIN); 245 | gpio_set_dir(RST_PIN, true); 246 | gpio_put(RST_PIN, false); 247 | 248 | gpio_set_pulls(RST_PIN, false, false); 249 | gpio_set_pulls(SWS_PIN, true, false); 250 | gpio_set_pulls(DBG_PIN, false, false); 251 | 252 | sws_tx_program_offset = pio_add_program(pio0, &sws_tx_program); 253 | set_tx_clock(10.0e6); 254 | 255 | sws_rx_program_offset = pio_add_program(pio1, &sws_rx_program); 256 | sws_rx_program_init(pio1, SM_RX, sws_rx_program_offset, SWS_PIN); 257 | pio_sm_set_enabled(pio1, SM_RX, true); 258 | 259 | banner(); 260 | for (;;) 261 | { 262 | int c = getchar(); 263 | switch (c) 264 | { 265 | case 'i': 266 | init_cmd(); 267 | break; 268 | 269 | case 'r': 270 | { 271 | int i = getchar() == '1'; 272 | printf("# reset <- %d\n", i); 273 | gpio_put(RST_PIN, i); 274 | if (i == 0) 275 | is_connected = false; 276 | printf("S\n"); 277 | break; 278 | } 279 | 280 | case 'g': 281 | { 282 | gpio_put(RST_PIN, 0); 283 | sleep_us(100); 284 | gpio_put(RST_PIN, 1); 285 | sleep_us(100); 286 | break; 287 | } 288 | 289 | case 's': 290 | { 291 | uint16_t socid = read_single_debug_word(reg_soc_id); 292 | printf("# socid = %04x\nS\n", socid); 293 | break; 294 | } 295 | 296 | case 'R': 297 | { 298 | uint16_t address = read_hex_word(); 299 | uint16_t count = read_hex_word(); 300 | 301 | if (count) 302 | { 303 | uint8_t b = read_first_debug_byte(address); 304 | printf("%02x", b); 305 | count--; 306 | 307 | while (count--) 308 | { 309 | b = read_next_debug_byte(); 310 | printf("%02x", b); 311 | } 312 | 313 | finish_reading_debug_bytes(); 314 | printf("\n"); 315 | } 316 | printf("S\n"); 317 | break; 318 | } 319 | 320 | case 'W': 321 | { 322 | uint16_t address = read_hex_word(); 323 | uint16_t count = read_hex_word(); 324 | 325 | if (count) 326 | { 327 | uint8_t b = read_hex_byte(); 328 | write_first_debug_byte(address, b); 329 | count--; 330 | 331 | while (count--) 332 | { 333 | b = read_hex_byte(); 334 | write_next_debug_byte(b); 335 | } 336 | 337 | finish_writing_debug_bytes(); 338 | } 339 | 340 | printf("S\n"); 341 | break; 342 | } 343 | 344 | case '?': 345 | banner(); 346 | break; 347 | 348 | default: 349 | printf("?\n"); 350 | printf("# unknown command\n"); 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/tusb_config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright (c) 2024 David Given 4 | * Copyright (c) 2021 Álvaro Fernández Rojas 5 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 6 | * Copyright (c) 2020 Damien P. George 7 | */ 8 | 9 | #if !defined(_TUSB_CONFIG_H_) 10 | #define _TUSB_CONFIG_H_ 11 | 12 | #include 13 | #include 14 | 15 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 16 | 17 | #define CFG_TUD_CDC 2 18 | #define CFG_TUD_CDC_RX_BUFSIZE 1024 19 | #define CFG_TUD_CDC_TX_BUFSIZE 1024 20 | 21 | extern void usbd_serial_init(void); 22 | 23 | #endif /* _TUSB_CONFIG_H_ */ 24 | -------------------------------------------------------------------------------- /src/usb-descriptors.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright (c) 2021 Álvaro Fernández Rojas 4 | * 5 | * This file is based on a file originally part of the 6 | * MicroPython project, http://micropython.org/ 7 | * 8 | * Copyright (c) 2024 David Given 9 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 10 | * Copyright (c) 2019 Damien P. George 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #define DESC_STR_MAX 23 17 | 18 | #define USBD_VID 0x2E8A /* Raspberry Pi */ 19 | #define USBD_PID 0x000A /* Raspberry Pi Pico SDK CDC */ 20 | 21 | #define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN * CFG_TUD_CDC) 22 | #define USBD_MAX_POWER_MA 500 23 | 24 | #define USBD_ITF_CDC_0 0 25 | #define USBD_ITF_CDC_1 2 26 | #define USBD_ITF_MAX 4 27 | 28 | #define USBD_CDC_0_EP_CMD 0x81 29 | #define USBD_CDC_1_EP_CMD 0x83 30 | 31 | #define USBD_CDC_0_EP_OUT 0x01 32 | #define USBD_CDC_1_EP_OUT 0x03 33 | 34 | #define USBD_CDC_0_EP_IN 0x82 35 | #define USBD_CDC_1_EP_IN 0x84 36 | 37 | #define USBD_CDC_CMD_MAX_SIZE 8 38 | #define USBD_CDC_IN_OUT_MAX_SIZE 64 39 | 40 | #define USBD_STR_0 0x00 41 | #define USBD_STR_MANUF 0x01 42 | #define USBD_STR_PRODUCT 0x02 43 | #define USBD_STR_SERIAL 0x03 44 | #define USBD_STR_SERIAL_LEN 17 45 | #define USBD_STR_CDC 0x04 46 | 47 | static const tusb_desc_device_t usbd_desc_device = { 48 | .bLength = sizeof(tusb_desc_device_t), 49 | .bDescriptorType = TUSB_DESC_DEVICE, 50 | .bcdUSB = 0x0200, 51 | .bDeviceClass = TUSB_CLASS_MISC, 52 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 53 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 54 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 55 | .idVendor = USBD_VID, 56 | .idProduct = USBD_PID, 57 | .bcdDevice = 0x0100, 58 | .iManufacturer = USBD_STR_MANUF, 59 | .iProduct = USBD_STR_PRODUCT, 60 | .iSerialNumber = USBD_STR_SERIAL, 61 | .bNumConfigurations = 1, 62 | }; 63 | 64 | static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { 65 | TUD_CONFIG_DESCRIPTOR(1, 66 | USBD_ITF_MAX, 67 | USBD_STR_0, 68 | USBD_DESC_LEN, 69 | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 70 | USBD_MAX_POWER_MA), 71 | 72 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_0, 73 | USBD_STR_CDC, 74 | USBD_CDC_0_EP_CMD, 75 | USBD_CDC_CMD_MAX_SIZE, 76 | USBD_CDC_0_EP_OUT, 77 | USBD_CDC_0_EP_IN, 78 | USBD_CDC_IN_OUT_MAX_SIZE), 79 | 80 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_1, 81 | USBD_STR_CDC, 82 | USBD_CDC_1_EP_CMD, 83 | USBD_CDC_CMD_MAX_SIZE, 84 | USBD_CDC_1_EP_OUT, 85 | USBD_CDC_1_EP_IN, 86 | USBD_CDC_IN_OUT_MAX_SIZE), 87 | }; 88 | 89 | static char usbd_serial[USBD_STR_SERIAL_LEN] = "000000000000"; 90 | 91 | static const char* const usbd_desc_str[] = { 92 | [USBD_STR_0] = nullptr, 93 | [USBD_STR_MANUF] = "Cowlark Technologies", 94 | [USBD_STR_PRODUCT] = "Telink debugger bridge", 95 | [USBD_STR_SERIAL] = usbd_serial, 96 | [USBD_STR_CDC] = "Board CDC", 97 | }; 98 | 99 | const uint8_t* tud_descriptor_device_cb(void) 100 | { 101 | return (const uint8_t*)&usbd_desc_device; 102 | } 103 | 104 | const uint8_t* tud_descriptor_configuration_cb(uint8_t index) 105 | { 106 | return usbd_desc_cfg; 107 | } 108 | 109 | const uint16_t* tud_descriptor_string_cb(uint8_t index, uint16_t langid) 110 | { 111 | static uint16_t desc_str[DESC_STR_MAX]; 112 | uint8_t len; 113 | 114 | if (index == 0) 115 | { 116 | desc_str[1] = 0x0409; 117 | len = 1; 118 | } 119 | else 120 | { 121 | const char* str; 122 | char serial[USBD_STR_SERIAL_LEN]; 123 | 124 | if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0])) 125 | return NULL; 126 | 127 | str = usbd_desc_str[index]; 128 | for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len) 129 | desc_str[1 + len] = str[len]; 130 | } 131 | 132 | desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * len + 2); 133 | 134 | return desc_str; 135 | } 136 | 137 | void usbd_serial_init(void) 138 | { 139 | uint8_t id[8]; 140 | 141 | flash_get_unique_id(id); 142 | 143 | snprintf(usbd_serial, 144 | USBD_STR_SERIAL_LEN, 145 | "%02X%02X%02X%02X%02X%02X%02X%02X", 146 | id[0], 147 | id[1], 148 | id[2], 149 | id[3], 150 | id[4], 151 | id[5], 152 | id[6], 153 | id[7]); 154 | } 155 | -------------------------------------------------------------------------------- /src/usb-uart.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright 2024 David Given 4 | * Copyright 2021 Álvaro Fernández Rojas 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "globals.h" 15 | 16 | #if !defined(MIN) 17 | #define MIN(a, b) ((a > b) ? b : a) 18 | #endif /* MIN */ 19 | 20 | #define LED_PIN 25 21 | 22 | #define BUFFER_SIZE 2560 23 | 24 | #define DEF_BIT_RATE 115200 25 | #define DEF_STOP_BITS 1 26 | #define DEF_PARITY 0 27 | #define DEF_DATA_BITS 8 28 | 29 | #define IF_CONTROL 0 30 | #define IF_DATA 1 31 | 32 | typedef struct 33 | { 34 | uart_inst_t* const inst; 35 | uint8_t tx_pin; 36 | uint8_t rx_pin; 37 | } uart_id_t; 38 | 39 | typedef struct 40 | { 41 | cdc_line_coding_t usb_lc; 42 | cdc_line_coding_t uart_lc; 43 | uint8_t uart_buffer[BUFFER_SIZE]; 44 | uint32_t uart_pos; 45 | uint8_t usb_buffer[BUFFER_SIZE]; 46 | uint32_t usb_pos; 47 | } uart_data_t; 48 | 49 | static void uart_read_bytes(uint8_t itf); 50 | static void uart_write_bytes(uint8_t itf); 51 | static void fifo_read_bytes(uint8_t itf); 52 | static void fifo_write_bytes(uint8_t itf); 53 | 54 | static const uart_id_t UART_ID[CFG_TUD_CDC] = { 55 | [IF_CONTROL] = 56 | { 57 | .inst = nullptr, 58 | }, 59 | [IF_DATA] = { 60 | .inst = uart0, 61 | .tx_pin = 0, 62 | .rx_pin = 1, 63 | } 64 | }; 65 | 66 | static uart_data_t UART_DATA[CFG_TUD_CDC]; 67 | 68 | queue_t rd_queue; 69 | queue_t wr_queue; 70 | 71 | static uint32_t databits_usb2uart(uint8_t data_bits) 72 | { 73 | switch (data_bits) 74 | { 75 | case 5: 76 | return 5; 77 | case 6: 78 | return 6; 79 | case 7: 80 | return 7; 81 | default: 82 | return 8; 83 | } 84 | } 85 | 86 | static uart_parity_t parity_usb2uart(uint8_t usb_parity) 87 | { 88 | switch (usb_parity) 89 | { 90 | case 1: 91 | return UART_PARITY_ODD; 92 | case 2: 93 | return UART_PARITY_EVEN; 94 | default: 95 | return UART_PARITY_NONE; 96 | } 97 | } 98 | 99 | static uint32_t stopbits_usb2uart(uint8_t stop_bits) 100 | { 101 | switch (stop_bits) 102 | { 103 | case 2: 104 | return 2; 105 | default: 106 | return 1; 107 | } 108 | } 109 | 110 | static void update_uart_cfg(uint8_t itf) 111 | { 112 | const uart_id_t* ui = &UART_ID[itf]; 113 | uart_data_t* ud = &UART_DATA[itf]; 114 | 115 | if (ud->usb_lc.bit_rate != ud->uart_lc.bit_rate) 116 | { 117 | uart_set_baudrate(ui->inst, ud->usb_lc.bit_rate); 118 | ud->uart_lc.bit_rate = ud->usb_lc.bit_rate; 119 | } 120 | 121 | if ((ud->usb_lc.stop_bits != ud->uart_lc.stop_bits) || 122 | (ud->usb_lc.parity != ud->uart_lc.parity) || 123 | (ud->usb_lc.data_bits != ud->uart_lc.data_bits)) 124 | { 125 | uart_set_format(ui->inst, 126 | databits_usb2uart(ud->usb_lc.data_bits), 127 | stopbits_usb2uart(ud->usb_lc.stop_bits), 128 | parity_usb2uart(ud->usb_lc.parity)); 129 | ud->uart_lc.data_bits = ud->usb_lc.data_bits; 130 | ud->uart_lc.parity = ud->usb_lc.parity; 131 | ud->uart_lc.stop_bits = ud->usb_lc.stop_bits; 132 | } 133 | } 134 | 135 | static void usb_read_bytes(uint8_t itf) 136 | { 137 | uart_data_t* ud = &UART_DATA[itf]; 138 | uint32_t len = tud_cdc_n_available(itf); 139 | 140 | if (len) 141 | { 142 | len = MIN(len, BUFFER_SIZE - ud->usb_pos); 143 | if (len) 144 | { 145 | uint32_t count; 146 | 147 | count = tud_cdc_n_read(itf, &ud->usb_buffer[ud->usb_pos], len); 148 | ud->usb_pos += count; 149 | } 150 | } 151 | } 152 | 153 | static void usb_write_bytes(uint8_t itf) 154 | { 155 | uart_data_t* ud = &UART_DATA[itf]; 156 | 157 | if (ud->uart_pos) 158 | { 159 | uint32_t count; 160 | 161 | count = tud_cdc_n_write(itf, ud->uart_buffer, ud->uart_pos); 162 | if (count < ud->uart_pos) 163 | memmove( 164 | ud->uart_buffer, &ud->uart_buffer[count], ud->uart_pos - count); 165 | ud->uart_pos -= count; 166 | 167 | if (count) 168 | tud_cdc_n_write_flush(itf); 169 | } 170 | } 171 | 172 | static void usb_cdc_process(uint8_t itf) 173 | { 174 | uart_data_t* ud = &UART_DATA[itf]; 175 | 176 | tud_cdc_n_get_line_coding(itf, &ud->usb_lc); 177 | 178 | usb_read_bytes(itf); 179 | usb_write_bytes(itf); 180 | } 181 | 182 | static void core1_entry(void) 183 | { 184 | tusb_init(); 185 | 186 | while (1) 187 | { 188 | int itf; 189 | int con = 0; 190 | 191 | tud_task(); 192 | 193 | for (itf = 0; itf < CFG_TUD_CDC; itf++) 194 | { 195 | /* Send/receive USB */ 196 | 197 | if (tud_cdc_n_connected(itf)) 198 | { 199 | con = 1; 200 | usb_cdc_process(itf); 201 | } 202 | 203 | /* Send/receive UARTs */ 204 | 205 | update_uart_cfg(IF_DATA); 206 | uart_read_bytes(IF_DATA); 207 | uart_write_bytes(IF_DATA); 208 | 209 | fifo_read_bytes(IF_CONTROL); 210 | fifo_write_bytes(IF_CONTROL); 211 | } 212 | 213 | gpio_put(LED_PIN, con); 214 | } 215 | } 216 | 217 | static void uart_read_bytes(uint8_t itf) 218 | { 219 | uart_data_t* ud = &UART_DATA[itf]; 220 | const uart_id_t* ui = &UART_ID[itf]; 221 | 222 | while (uart_is_readable(ui->inst) && (ud->uart_pos < BUFFER_SIZE)) 223 | { 224 | ud->uart_buffer[ud->uart_pos] = uart_getc(ui->inst); 225 | ud->uart_pos++; 226 | } 227 | } 228 | 229 | static void fifo_read_bytes(uint8_t itf) 230 | { 231 | uart_data_t* ud = &UART_DATA[itf]; 232 | const uart_id_t* ui = &UART_ID[itf]; 233 | 234 | while (ud->uart_pos < BUFFER_SIZE) 235 | { 236 | if (!queue_try_remove(&wr_queue, &ud->uart_buffer[ud->uart_pos])) 237 | break; 238 | 239 | ud->uart_pos++; 240 | } 241 | } 242 | 243 | static void uart_write_bytes(uint8_t itf) 244 | { 245 | uart_data_t* ud = &UART_DATA[itf]; 246 | 247 | if (ud->usb_pos) 248 | { 249 | const uart_id_t* ui = &UART_ID[itf]; 250 | uint32_t count = 0; 251 | 252 | while (uart_is_writable(ui->inst) && count < ud->usb_pos) 253 | { 254 | uart_putc_raw(ui->inst, ud->usb_buffer[count]); 255 | count++; 256 | } 257 | 258 | if (count < ud->usb_pos) 259 | memmove( 260 | ud->usb_buffer, &ud->usb_buffer[count], ud->usb_pos - count); 261 | ud->usb_pos -= count; 262 | } 263 | } 264 | 265 | static void fifo_write_bytes(uint8_t itf) 266 | { 267 | uart_data_t* ud = &UART_DATA[itf]; 268 | 269 | if (ud->usb_pos) 270 | { 271 | uint32_t count = 0; 272 | 273 | while (count < ud->usb_pos) 274 | { 275 | 276 | if (!queue_try_add(&rd_queue, &ud->usb_buffer[count])) 277 | break; 278 | count++; 279 | } 280 | 281 | if (count < ud->usb_pos) 282 | memmove( 283 | ud->usb_buffer, &ud->usb_buffer[count], ud->usb_pos - count); 284 | ud->usb_pos -= count; 285 | } 286 | } 287 | 288 | static void init_uart_data(uint8_t itf) 289 | { 290 | const uart_id_t* ui = &UART_ID[itf]; 291 | uart_data_t* ud = &UART_DATA[itf]; 292 | 293 | /* USB CDC LC */ 294 | ud->usb_lc.bit_rate = DEF_BIT_RATE; 295 | ud->usb_lc.data_bits = DEF_DATA_BITS; 296 | ud->usb_lc.parity = DEF_PARITY; 297 | ud->usb_lc.stop_bits = DEF_STOP_BITS; 298 | 299 | /* UART LC */ 300 | ud->uart_lc.bit_rate = DEF_BIT_RATE; 301 | ud->uart_lc.data_bits = DEF_DATA_BITS; 302 | ud->uart_lc.parity = DEF_PARITY; 303 | ud->uart_lc.stop_bits = DEF_STOP_BITS; 304 | 305 | /* Buffer */ 306 | ud->uart_pos = 0; 307 | ud->usb_pos = 0; 308 | 309 | /* UART start */ 310 | if (ui->inst) 311 | { 312 | /* Pinmux */ 313 | gpio_set_function(ui->tx_pin, GPIO_FUNC_UART); 314 | gpio_set_function(ui->rx_pin, GPIO_FUNC_UART); 315 | 316 | uart_init(ui->inst, ud->usb_lc.bit_rate); 317 | uart_set_hw_flow(ui->inst, false, false); 318 | uart_set_format(ui->inst, 319 | databits_usb2uart(ud->usb_lc.data_bits), 320 | stopbits_usb2uart(ud->usb_lc.stop_bits), 321 | parity_usb2uart(ud->usb_lc.parity)); 322 | uart_set_fifo_enabled(ui->inst, true); 323 | } 324 | } 325 | 326 | void usb_bridge_init(void) 327 | { 328 | usbd_serial_init(); 329 | 330 | for (int itf = 0; itf < CFG_TUD_CDC; itf++) 331 | init_uart_data(itf); 332 | 333 | queue_init(&rd_queue, 1, 256); 334 | queue_init(&wr_queue, 1, 256); 335 | multicore_launch_core1(core1_entry); 336 | } 337 | --------------------------------------------------------------------------------