Merge pull request #23 from armel/feature_update_v4

Feature update v4
This commit is contained in:
Armel FAUVEAU authored and GitHub committed 2025-12-06 23:07:28 +01:00
commit 78aeedc251
34 files changed
+603

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+119
View File
@@ -0,0 +1,119 @@
# Unbricking the UV-K5 V1
<img width="2016" height="1512" alt="pcbite" src="https://github.com/user-attachments/assets/b3086bf2-c14c-47da-b68b-a6867268ae78" />
Restoring a UV-K5 V1 accidentally flashed with the F4HWN 4.3 Fusion 🔥 Edition firmware, using OpenOCD and a ST-LINK programmer.
## Introduction
Some users have accidentally flashed a UV-K5 V1 with the F4HWN 4.3 Fusion 🔥 Edition firmware. This edition **is only** compatible with:
- UV-K5 V3
- UV-K1
When flashed onto a UV-K5 V1, it causes a complete brick, such as:
- The radio does not power on anymore
- DFU mode is unavailable
- No LED activity
- Flashing tools cannot detect the device
Fortunately, the UV-K5 V1 can be fully restored by using OpenOCD and a ST-LINK programmer.
## Requirements
You will need:
- A ST-LINK V2 programmer (original or clone)
- Four Dupont jumper wires
- A small screwdriver to open the radio
- A computer with OpenOCD installed (Windows / macOS / Linux).
## SWD connection points on the UV-K5 V1
The radio must be opened to access the front side of the PCB.
The UV-K5 V1 exposes a 4-pin SWD (Serial Wire Debug) interface:
|Signal| UV-K5 V1 Pad| ST-LINK Pin|
|:-------- |:--------:| --------:|
|GND |GND pad | GND |
|SWCLK |SWCLK pad | SWCLK
|SWDIO |SWDIO pad | SWDIO
|3.3V | VCC pad | 3.3V |
<img width="750" height="500" alt="bottom" src="https://github.com/user-attachments/assets/381a9bc5-daac-4547-820e-a5c96c0bd2e1" />
> [!WARNING]
> Do not connect the battery while using the ST-LINK.
The ST-LINK provides 3.3V to the board.
## Installing OpenOCD
### macOS (Homebrew)
`brew install openocd`
### Windows
Download the official build from:
- [Website](https://openocd.org/pages/getting-openocd.html)
- [GitHub](https://github.com/openocd-org/openocd/releases)
### Linux (Ubuntu / Debian)
`sudo apt install openocd`
### Verify installation
`openocd --version`
## Download unbrick toolkit
Download the [unbrick toolkit archive](https://github.com/user-attachments/files/24002834/unbrick_k5_v1.zip) and extract it on your PC.
## Unbrick procedure
### Connect the ST-LINK
1. Connect the ST-LINK pins to the SWD pads:
* 3.3V → 3.3V
* SWDIO → SWDIO
* SWCLK → SWCLK
* GND → GND
2. Plug the ST-LINK into your computer.
3. Power on your UV-K5 V1 (normal mode).
### Flash the bootloader
From the `unbrick_k5_v1` directory:
#### Option A — Use the helper script
`
./unbrick_k5_v1.sh
`
#### Option B — Run the OpenOCD command manually
`
openocd -f ./interface/stlink.cfg -f ./target/dp32g030.cfg -c "init; reset halt; uv_flash_bl bootloader.bin; shutdown"
`
https://github.com/user-attachments/assets/a511fdb3-a3a3-4fe1-91cc-31e765221b22
If no errors appear, the bootloader has been successfully restored.
- Disconnect and remove the ST-LINK from your UV-K5 V1 SWD port
- Reinstall the battery
- Power on the radio on DFU mode
The device should now start correctly on DFU mode again. You can then flash stock firmware, or the [F4HWN firmware for UV-K5 V1](https://github.com/armel/uv-k5-firmware-custom) (not the Fusion 🔥 Edition).
# Disclaimer
This procedure requires opening the device and directly manipulating its microcontroller over SWD. Incorrect use may permanently damage the radio. Proceed at your own risk.
Binary file not shown.
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# STMicroelectronics ST-LINK/V1, ST-LINK/V2, ST-LINK/V2-1, STLINK-V3 in-circuit
# debugger/programmer
#
# This new interface driver creates a ST-Link wrapper for ARM-DAP named "dapdirect"
# Old ST-LINK/V1 and ST-LINK/V2 pre version V2J24 don't support "dapdirect"
#
# SWIM transport is natively supported
#
adapter driver st-link
st-link vid_pid 0x0483 0x3744 0x0483 0x3748 0x0483 0x374b 0x0483 0x374d 0x0483 0x374e 0x0483 0x374f 0x0483 0x3752 0x0483 0x3753 0x0483 0x3754 0x0483 0x3755 0x0483 0x3757
# transport select dapdirect_jtag
# transport select dapdirect_swd
# transport select swim
# Optionally specify the serial number of usb device
# e.g.
# adapter serial "\xaa\xbc\x6e\x06\x50\x75\xff\x55\x17\x42\x19\x3f"
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-or-later
echo "WARNING: interface/stlink-v1.cfg is deprecated, please switch to interface/stlink.cfg"
source [find interface/stlink.cfg]
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-or-later
echo "WARNING: interface/stlink-v2-1.cfg is deprecated, please switch to interface/stlink.cfg"
source [find interface/stlink.cfg]
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-or-later
echo "WARNING: interface/stlink-v2.cfg is deprecated, please switch to interface/stlink.cfg"
source [find interface/stlink.cfg]
+18
View File
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# STMicroelectronics ST-LINK/V1, ST-LINK/V2, ST-LINK/V2-1, STLINK-V3 in-circuit
# debugger/programmer
#
adapter driver hla
hla layout stlink
hla device_desc "ST-LINK"
hla vid_pid 0x0483 0x3744 0x0483 0x3748 0x0483 0x374b 0x0483 0x374d 0x0483 0x374e 0x0483 0x374f 0x0483 0x3752 0x0483 0x3753 0x0483 0x3754 0x0483 0x3755 0x0483 0x3757
# Optionally specify the serial number of ST-LINK/V2 usb device. ST-LINK/V2
# devices seem to have serial numbers with unreadable characters. ST-LINK/V2
# firmware version >= V2.J21.S4 recommended to avoid issues with adapter serial
# number reset issues.
# eg.
#adapter serial "\xaa\xbc\x6e\x06\x50\x75\xff\x55\x17\x42\x19\x3f"
Binary file not shown.

After

Width:  |  Height:  |  Size: 685 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.
+359
View File
@@ -0,0 +1,359 @@
#OpenOCD script for Action Dynamic DP32G030 ARM Cortex M0 CPU (UV-5R, UV-k5 Ham HTs)
#For use with cheap ST-Link USB debug probe
source target/swj-dp.tcl
set _CHIP_NAME DP32G0xx
set _ENDIAN little
set _WORKAREASIZE 0x1000
set _FLASH_SIZE 0x10000
set _CPUTAPID 0x0BB11477
set _TARGETNAME $_CHIP_NAME.cpu
set _FLASHNAME $_CHIP_NAME.flash
set _SECTOR_SIZE 512
set _MASKING_CFG 2 ;#1:2kB, 2:4kB, 3:8kB
adapter speed 960
adapter srst delay 100
reset_config srst_nogate
# Create a new dap, with name chip and role CPU, -enable let's OpenOCD to know to add it to the scan
swj_newdap $_CHIP_NAME cpu -expected-id $_CPUTAPID -enable
# Create the DAP instance, this must be explicitly created according to the OpenOCD docs
dap create $_CHIP_NAME.dap -chain-position $_CHIP_NAME.cpu
# Set up the GDB target for the CPU
target create $_CHIP_NAME.cpu cortex_m -endian $_ENDIAN -dap $_CHIP_NAME.dap
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
# Declare internal bank
flash bank $_FLASHNAME stm32f1x 0x08000000 $_FLASH_SIZE 0 0 $_TARGETNAME
proc check_readiness {} {
while {[read_memory 0x4006F014 32 1] & 0x2} {}
}
proc rom_mask_off {} {
echo "\nChecking ROM masking"
check_readiness
set status [read_memory 0x4006F020 32 1]
if {($status & 0x3) != 0} {
echo [format "\nROM masking is set to 0b%03b. Unsetting..." $status]
write_memory 0x4006F020 32 [expr {[read_memory 0x4006F020 32 1] & 0x3}]
check_readiness
write_memory 0x4006F020 32 0
check_readiness
write_memory 0x4006F020 32 4
}
return [read_memory 0x4006F020 32 1]
}
proc rom_mask_on {} {
global _MASKING_CFG
echo "\nChecking ROM masking"
check_readiness
set status [read_memory 0x4006F020 32 1]
if {($status & 0x3) != $_MASKING_CFG} {
echo [format "\nROM masking is set to 0b%03b. Setting ON..." $status]
write_memory 0x4006F020 32 [expr {[read_memory 0x4006F020 32 1] & 0x3}]
check_readiness
write_memory 0x4006F020 32 $_MASKING_CFG
check_readiness
write_memory 0x4006F020 32 [expr {4 | $_MASKING_CFG}]
}
return [read_memory 0x4006F020 32 1]
}
proc unlock_rom {} {
write_memory 0x4006F01c 32 0xAA
check_readiness
}
proc lock_rom {} {
write_memory 0x4006F018 32 0x55
check_readiness
}
proc select_region {target_r} {
#Region 0 is main user ROM area, 1 is NVRAM area
write_memory 0x4006F000 32 [expr {(0x31 & [read_memory 0x4006F000 32 1]) | (($target_r & 0x1) << 1)}]
check_readiness
}
proc wipe_sector_range {st_sec sec_count} {
set last [expr {$st_sec + $sec_count}]
set reg [expr {[read_memory 0x4006F000 32 1] & 0x7FFFFFFF}]
write_memory 0x4006F000 32 [expr {$reg | 0x8}] ;#set writing mode ERASE
for {set i $st_sec} {$i < $last} {incr i} {
check_readiness
echo -n [format "\rErasing sector 0x%02x = offset 0x%04x" [expr {$i}] [expr {$i*512}] ]
write_memory 0x4006F004 32 [expr {$i << 7}] ;#set address in flash
write_memory 0x4006F010 32 0x01 ;#do it
}
check_readiness
write_memory 0x4006F000 32 $reg
}
proc wipe_rom {} {
#This will wipe everything including bootloader
global _SECTOR_SIZE
global _FLASH_SIZE
unlock_rom
select_region 0
if {[rom_mask_off] != 4} {
echo "\nROM Masking failed to disable!"
close $fd
return
}
wipe_sector_range 0 [expr {$_FLASH_SIZE / $_SECTOR_SIZE}]
}
proc binary_to_int {data} {
# Complète la chaîne à 4 octets si nécessaire
set data $data[string repeat \xFF [expr {4 - [string length $data]}]]
# Initialise le résultat à 0
set result 0
# Parcourt les octets et assemble l'entier
for {set i 0} {$i < 4} {incr i} {
# Récupère l'octet à la position $i
set byte [scan [string index $data $i] %c]
# Décale l'octet en fonction de l'ordre Little Endian
set result [expr {$result | ($byte & 0xFF) << (8 * $i)}]
}
return $result
}
proc write_image {filename offset} {
global _SECTOR_SIZE
global _FLASH_SIZE
set fs [file size $filename]
set fd [open $filename "rb"]
set reg [expr {[read_memory 0x4006F000 32 1] & 0x7FFFFFFF}]
write_memory 0x4006F000 32 [expr {$reg | 0x4}] ;#set writing mode PROGRAM
while {![eof $fd]} {
if {($offset+4) > $_FLASH_SIZE} {
echo "\nData exceeds main storage capacity!"
write_memory 0x4006F000 32 $reg
lock_rom
close $fd
return
}
check_readiness
set data [read $fd 4]
set data $data[string repeat \xFF [expr {4-[string length $data]}]] ;#padding
#binary scan $data i i_data
set i_data [binary_to_int $data]
write_memory 0x4006F004 32 [expr {($offset>>2)+0xC000}] ;#set destination offset
write_memory 0x4006F008 32 $i_data ;#set word
write_memory 0x4006F010 32 0x01 ;#set OPSTART=1
while {([read_memory 0x4006F014 32 1] & 4) == 0} {}
echo -n [format "\rProgrammed up to 0x%04x (FLASH_ADDR=0x%04x)" $offset [expr {($offset>>2)+0xC000}]]
incr offset 4
}
check_readiness
write_memory 0x4006F000 32 $reg ;#reset writing mode to OFF
}
proc flash_blocks {filename address nblocks offset} {
#Intended for speed. Due to tight timings, sometimes it works, sometimes it does not. Needs clocks adjusting there.
global _SECTOR_SIZE
global _FLASH_SIZE
if {($nblocks != 0) & [expr {$nblocks & 1}]} {
set nblocks [expr {$nblocks + 1}]
}
set addr [expr {$_SECTOR_SIZE * ($address >> 9)}]
set fs [expr {((($_SECTOR_SIZE*$nblocks)/2 + $_SECTOR_SIZE-1)&(0x10000000-$_SECTOR_SIZE))}]
set fd [open $filename "rb"]
read $fd $addr
set addr [expr {$addr + $offset}] ;#apply ROM offset
set reg [expr {[read_memory 0x4006F000 32 1] & 0x7FFFFFFF}]
echo -n [format "\tWiping %02d sectors, starting at %02d " [expr {$nblocks / 2}] [expr {$addr >> 9}]]
wipe_sector_range [expr {$addr >> 9}] [expr {$nblocks / 2}] ;#wipe related sectors
echo "\nRegion cleared OK"
echo [format "%02d bytes to push" $fs]; ##DEBUG
write_memory 0x4006F000 32 [expr {$reg | 0x4}] ;#set writing mode PROGRAM
while {$fs > 0} {
write_memory 0x4006F004 32 [expr {0xC000+(($addr)>>2)}] ;#set block starting offset
set i_buffer {}
for {set blk 0} {$blk < $_SECTOR_SIZE/2} {incr blk 4} {
set data [read $fd 4]
set data $data[string repeat \xFF [expr {4-[string length $data]}]] ;#padding to desired ending block
if {($addr+$_SECTOR_SIZE/2) >= $_FLASH_SIZE} {
echo [format "\nMain firmware image upper boundary reached (%d)!" $addr]
write_memory 0x4006F000 32 $reg ;#reset writing mode to OFF
close $fd
return
}
binary scan $data i i_data
lappend i_buffer [expr {$i_data & 0xFFFFFFFF}]
incr fs -4
}
echo [format "\nWriting at offset 0x%04x" [expr {$addr}]]
##for {set bi 0} {$bi < 64} {incr bi} {echo -n [format "%08x" [lindex $i_buffer $bi]]}; #DEBUG
write_memory 0x4006F008 32 [lindex $i_buffer 0] ;#prepare 1st word: we need to be quick beyond this point
check_readiness
write_memory 0x4006F010 32 0x01
for {set bi 1} {$bi < 64} {incr bi} {while {([read_memory 0x4006F014 32 1] & 0x4) == 4} {write_memory 0x4006F008 32 [lindex $i_buffer $bi]}}
check_readiness
incr addr $_SECTOR_SIZE/2 ;# Next block
}
write_memory 0x4006F000 32 $reg ;#reset writing mode to OFF
echo [format "\nLast write was 0x%08x " [lindex $i_buffer 63]]
close $fd
return
}
proc toggle_pin_gpioa {pin} {
write_memory 0x40060000 16 [expr {[read_memory 0x40060000 16 1] ^(1<<$pin) }]
}
proc toggle_pin_gpiob {pin} {
write_memory 0x40060800 16 [expr {[read_memory 0x40060800 16 1] ^(1<<$pin) }]
}
proc toggle_pin_gpioc {pin} {
write_memory 0x40061000 16 [expr {[read_memory 0x40061000 16 1] ^(1<<$pin) }]
}
proc set_pin_gpioa {pin value} {
if {$value == 0} {
write_memory 0x40060000 16 [expr {[read_memory 0x40060000 16 1] &~(1<<$pin) }]
} else {
write_memory 0x40060000 16 [expr {[read_memory 0x40060000 16 1] |(1<<$pin) }]
}
}
proc set_pin_gpiob {pin value} {
if {$value == 0} {
write_memory 0x40060800 16 [expr {[read_memory 0x40060800 16 1] &~(1<<$pin) }]
} else {
write_memory 0x40060800 16 [expr {[read_memory 0x40060800 16 1] |(1<<$pin) }]
}
}
proc set_pin_gpioc {pin value} {
if {$value == 0} {
write_memory 0x40061000 16 [expr {[read_memory 0x40061000 16 1] &~(1<<$pin) }]
} else {
write_memory 0x40061000 16 [expr {[read_memory 0x40061000 16 1] |(1<<$pin) }]
}
}
##Quansheng UVK5-specific snippets
proc uv_fastflash_bl {filename} {
write_memory 0x4006F024 32 0x4E02A300 ;#force stock timings, just in case
write_memory 0x4006F028 32 0x210360
write_memory 0x4006F000 32 0x1
check_readiness
select_region 0
if {[rom_mask_off] != 4} {
echo "\nROM Masking failed to disable!"
close $fd
return
}
reset halt
unlock_rom
flash_blocks $filename 0 16 0
#just relock flashROM
lock_rom
}
proc uv_fastflash_fw {filename} {
#Make sure bootloader is hidden
if {[rom_mask_on] != 6} {
echo "\nROM Masking failed to enable!"
close $fd
return
}
reset halt
unlock_rom
flash_blocks $filename 0 [expr {[file size $filename] >> 8}] 0
reset
echo "\nCPU reset: Transceiver should boot now."
}
proc uv_flash_bl {filename} {
#Securely rewrites bootloader (slowly)
if {[file size $filename] > 0x1000} {
echo [format "Bootloader image is too large to fit!]
return
}
select_region 0
if {[rom_mask_off] != 4} {
echo "\nROM Masking failed to disable!"
return
}
reset halt
unlock_rom
wipe_sector_range 0 8
echo "\nRegion cleared OK"
write_image $filename 0
if {[rom_mask_on] != 6} {
echo "\nROM Masking failed to enable!"
lock_rom
return
}
#relock flashROM, in case conventional method for fw is preferred
lock_rom
echo "\nBootloader code programmed.\nYou can use uv_flash_fw to program main firmware, or just use stock tool to do it."
}
proc uv_flash_fw {filename} {
#Securely rewrites main firmware (slowly)
select_region 0
#Make sure bootloader is hidden
if {[rom_mask_on] != 6} {
echo "\nROM Masking failed to enable!"
return
}
reset halt
unlock_rom
wipe_sector_range 0 120
echo "\nRegion cleared OK"
write_image $filename 0
#relock flashROM, then reset CPU
lock_rom
reset
echo "\nCPU reset: Transceiver should boot now."
}
proc uv_flashlight_toggle {} {
toggle_pin_gpioc 3 ;# toggles PORTC.3
}
proc uv_flashlight_on {} {
set_pin_gpioc 3 1 ;# set PORTC.3 high
}
proc uv_flashlight_off {} {
set_pin_gpioc 3 0 ;# set PORTC.3 to low
}
proc uv_backlight_toggle {} {
toggle_pin_gpiob 6 ;# toggles PORTB.6
}
init
#reset halt
@@ -0,0 +1,34 @@
# ARM Debug Interface V5 (ADI_V5) utility
# ... Mostly for SWJ-DP (not SW-DP or JTAG-DP, since
# SW-DP and JTAG-DP targets don't need to switch based
# on which transport is active.
#
# declare a JTAG or SWD Debug Access Point (DAP)
# based on the transport in use with this session.
# You can't access JTAG ops when SWD is active, etc.
# params are currently what "jtag newtap" uses
# because OpenOCD internals are still strongly biased
# to JTAG .... but for SWD, "irlen" etc are ignored,
# and the internals work differently
# for now, ignore non-JTAG and non-SWD transports
# (e.g. initial flash programming via SPI or UART)
# split out "chip" and "tag" so we can someday handle
# them more uniformly irlen too...)
if [catch {transport select}] {
echo "Error: unable to select a session transport. Can't continue."
shutdown
}
proc swj_newdap {chip tag args} {
if [using_hla] {
eval hla newtap $chip $tag $args
} elseif [using_jtag] {
eval jtag newtap $chip $tag $args
} elseif [using_swd] {
eval swd newdap $chip $tag $args
}
}
+37
View File
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# ARM Debug Interface V5 (ADI_V5) utility
# ... Mostly for SWJ-DP (not SW-DP or JTAG-DP, since
# SW-DP and JTAG-DP targets don't need to switch based
# on which transport is active.
#
# declare a JTAG or SWD Debug Access Point (DAP)
# based on the transport in use with this session.
# You can't access JTAG ops when SWD is active, etc.
# params are currently what "jtag newtap" uses
# because OpenOCD internals are still strongly biased
# to JTAG .... but for SWD, "irlen" etc are ignored,
# and the internals work differently
# for now, ignore non-JTAG and non-SWD transports
# (e.g. initial flash programming via SPI or UART)
# split out "chip" and "tag" so we can someday handle
# them more uniformly irlen too...)
if [catch {transport select}] {
echo "Error: unable to select a session transport. Can't continue."
shutdown
}
proc swj_newdap {chip tag args} {
if [using_jtag] {
eval jtag newtap $chip $tag $args
} elseif [using_swd] {
eval swd newdap $chip $tag $args
} else {
echo "Error: transport '[ transport select ]' not supported by swj_newdap"
shutdown
}
}
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
openocd -f ./interface/stlink.cfg -f ./target/dp32g030.cfg -c "init; reset halt; uv_flash_bl bootloader.bin; shutdown"