├── .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 |
--------------------------------------------------------------------------------