Обновлено.
- Подпись автора
USMicro® 2026©
USMI |
Привет, Гость! Войдите или зарегистрируйтесь.
Вы здесь » USMI » MCU, SoC, CPU Микроконтроллеры » Bluetrum SoC
Обновлено.
USMicro® 2026©
Вы записываете на другой чип. На другой чип просто так без перезборки или перешивровки как я понял XCOD региона, дамп просто так нельзя просто записать.
Я как раз работаю над этим.
USMicro® 2026©
Bạn đang ghi dữ liệu vào một chip khác. Theo như tôi hiểu, bạn không thể chỉ ghi dữ liệu sao lưu vào một chip khác mà không cần lắp ráp lại hoặc nạp lại vùng XCOD.
Tôi hiện đang nghiên cứu vấn đề này.
Да, именно так. Я считал данные и записал их в аналогичную плату, но с другим CODE KEY, и сейчас она не работает.
Я вижу, что ты тоже прошивал два чипа с разными CODE KEY.
Разобрался, причем сам)
Все просто оказалось. ЩАс перепроверю все и покажу что и как.
USMicro® 2026©
Так, обнаружил проблему.
Распаковал, запаковал штатным Xmaker. Прошился - вроде все ок. Но не работают кнопки и некоторые функции, глюки, зависания.
Дело в том, что в распакованных прошивках нет xcfg.bin. А он и отвечает за всякое. Это типа файл с настройками IO и тд.
Для полной создания полной прошивки DCF нужны следующие файлы -
header.bin,
app.bin,
res.bin,
xcfg.bin,
updater.bin
------------------------------------------------------------------------------------------
Тоесть в распаковщик нужно добавить парсинг xcfg.bin.
USMicro® 2026©
Проще будет перешифровать XCOD прям в дампе, чем разбирать его полностью. Этот xcfg.bin какой то странный. Не понятно где он, часть в начале дампа где имя БТ, а вторая вообще фиг знает.
USMicro® 2026©
Проще будет перешифровать XCOD прям в дампе, чем разбирать его полностью
Разобрался - перешифровка XCOD работает.
Нужно узнать codekey от дампа старого чипа, и от нового (зашив в них что то их под сдк), перешифровав с старого на новый - прошить, заработало.
С этим понятно. Но есть НО -
Некоторые прошивки содержат в себе NV Data region. Их несколько
Там хранится - XCFG, но не в том виде что брал сборщик SDK, он как то шифруется. Не так как XCOD area.
xcfg NV в дампе зашифрован аппаратным DMA ключом.
И вот с этим пока проблема.
USMicro® 2026©
Xmaker использует следующее -
make(dcf_buf, header.bin, app.bin, res.bin, xcfg.bin, updater.bin);
save(dcf_buf, app.dcf);
Он берет готовые бинарники и сшивает их. В дампе мы находим header.bin, app.bin, res.bin, - но не xcfg.bin, updater.bin.
USMicro® 2026©
Нашел!
Некоторые прошивки содержат NV xcfg если при сборке в app.xm были следующие параметры - setflash(1, 0x100000, 1, 1, 1).
Когда setflash(..., 0) — xcfg хранится только в DCF. Кнопки/GPIO конфигурируются через app code напрямую.
Когда setflash(..., 1) — xcfg пишется в NV область flash. Прошивка при старте читает его оттуда. Без него кнопки/BT не работают.
USMicro® 2026©
Привет всем! Удалось ли кому-нибудь из вас отладить микросхему серии JAZZ?
SnowPoke
Что именно интересует?
USMicro® 2026©
Bluetrum_Flash_Tool v1 - Для Windows. Без установки. Без привязки к питону или чему либо.
Скачал - запустил - подключил - работает.
Просто выбрав в настройках нужный порт. и скорость. В зависимости от UART чипа. Если происходят ошибки синхро или чтения/записи - понизить скорость.
Пока v1. Я тестировал не все, пару чипов почитал/пописал.
https://drive.google.com/file/d/1vlCo78 … drive_link
USMicro® 2026©
kagaimiq
Вопрос к тебе, какой в реале в JL формат Efuse Key? 0xFFFF Или это производное crc от ff ff ff ff битов?
Я щас расколупал чтение и перешифровку XCOD региона на горячую прям на лету, что бы ручками не делать ничего,
Я думаю лучше сделать представление как в твоих прогах JL...
Тут в bluetrum Fuse имеют формат
Chip ID: b'BLUECRWN\x01\x00\x00\x00'
Load address: $00012000
Init. commkey: $10C3890C
New commkey: $AAC1330E
Changing baudrate to 921600 baud...
- Flash JEDEC ID: 856014
- Flash UID: 415034553635330300a7ec4907103678
- Flash size: 1048576 bytes
- App codekey (XCOD): 444BF5C3
- eFuse Key: 0x5B2A
Как я понял - нужно прожигать то что в самих efuse, ити достаточно того же самого crc?
USMicro® 2026©
Фикс для downloader.py для работы с чипами серии EPIC.
Чтение работает отлично, а вот запись работает странно: бьются следующие области:
0x00000–0x0FFFF OK
0x10000–0x1FFFF битый
0x20000–0x2FFFF OK
0x30000–0x3FFFF битый
0x40000–0x4FFFF OK
0x50000–0x5FFFF битый
0x60000 + в основном OK.
Так что при записи лучше перепроверять и перезаписывать битые области.
from bluetrum.cipher import ab_calckey
from bluetrum.utils import *
import struct
import argparse
from base64 import b64decode
from tqdm import tqdm
###############################################################################
try:
from serial import Serial
from bluetrum.dl.uart import UARTDownload
have_uart = True
except ImportError:
have_uart = False
try:
from scsiio import SCSIDev
have_scsi = True
except ImportError:
have_scsi = False
if not have_uart and not have_scsi:
print('No available ways to communicate with the hardware.')
print('Install pyserial for UART or rip "scsiio" from jl-uboot-tool for USB MSC.')
exit(1)
###############################################################################
ap = argparse.ArgumentParser(description='Tool to communicate with the bootloader in the Bluetrum chips.')
if have_uart:
ap.add_argument('--init-baud', type=int, default=115200,
help='Initial baudrate (default: %(default)d baud)')
ap.add_argument('--baud', type=int, default=921600,
help='Baudrate to use (default: %(default)d baud)')
ap.add_argument('--port',
help='Serial port to use for UART bootloader')
ap.add_argument('--no-echo', action='store_true', default=False,
help='Disable echo (for CRWN/AB530X chips)')
if have_scsi:
ap.add_argument('--mscdev',
help='USB MSC (SCSI) device to use for USB bootloader')
ap.add_argument('-r', '--reboot', action='store_true',
help='Reboot the chip after completion')
ap.add_argument('--debug', action='store_true', default=False,
help='Enable extra diagnostic output')
actsp = ap.add_subparsers(dest='action')
asp_erase = actsp.add_parser('erase', help='Erase one or more flash areas')
asp_erase.add_argument('areas', metavar='address size', nargs='+',
help='Erase <size> bytes at <address>.'
' If <size> is 0, assumed to be whole flash.')
asp_read = actsp.add_parser('read', help='Read the flash into the file')
asp_read.add_argument('areas', metavar='address size file', nargs='+',
help='Read <size> bytes from <address> into <file>.'
' If <size> is 0, assumed to be whole flash.')
asp_write = actsp.add_parser('write', help='Write the file into flash')
asp_write.add_argument('areas', metavar='address file', nargs='+',
help='Write <file> starting at <address>.')
args = ap.parse_args()
###############################################################################
# PRAO (AB560x) — hand-built blob; bytes [4..23] are reserved for
# chipid / iface / blocksize injection at load time.
dl_blob = b64decode(
"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"
)
# CRWN (AB530x) — decrypted uartdown-001.dll.
# Bytes [4..23] contain live UART helper functions used by the blob itself;
# do NOT overwrite them at load time (unlike the PRAO blob).
dl_blob_crwn = b64decode(
"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"
"AAAAAAAAAAAAAAAA"
)
###############################################################################
# EPIC (AB532x / BLUEEPIC) — extracted from official Downloader v2.2.0
# during a real erase+write session. On-wire data (already prepared).
# Do NOT patch header at load time (same as CRWN).
dl_blob_epic = b64decode(
"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"
)
class BlCmd:
IFACE_PARAM = 0x50
AUTHORIZE = 0x55
MEM_WRITE = 0x57
SET_CMD_HANDLER = 0x58
GET_INFO = 0x5A
REBOOT = 0x5E
class NitDlCmd:
"""Command codes for PRAO (AB560x) blob."""
INIT = 0x00
DEV_READ = 0x01
DEV_WRITE = 0x02
DEV_ERASE = 0x03
class NitDlCmdCRWN:
"""Command codes for CRWN (AB530x) blob (uartdown-001.dll)."""
INIT = 0x00 # returns 4 bytes: [status, ?, density, ?]
DEV_READ = 0x28 # arg1=addr arg3=n_blocks(x512B) recv=n_blocks*512
DEV_WRITE = 0x2a # arg1=addr arg3=1 send=512B recv=1 (one block)
DEV_ERASE = 0x2f # arg1=addr arg3=1 recv=512 (one 64K block)
GET_INFO = 0x25 # recv=16 → bytes [0xC..0xE] = JEDEC device ID
def make_cb(cmd, arg1=0, arg2=0, arg3=0):
return struct.pack('>BIBH', cmd, arg1, arg2, arg3)
def patch_crwn_blob(blob_bytes):
"""
Fix six bugs in the CRWN blob (uartdown-001.dll).
Each patch replaces one wrong instruction with the correct one.
All offsets are relative to the start of the decrypted blob binary.
P3 READ 0x0B3C BLTU x21,x18 → NOP
Removes a hardcoded address ceiling (0x1E00) that caused the READ
loop to return zeros for any flash address >= 0x1E00.
P4a INIT 0x04D6 addi a5,a5,-0x1C4 → addi a5,a5,+0x2D6
In the flash-detection fallback path, INIT stores a do-nothing stub
at all iface function slots. This patch redirects the iface[0x10]
store to the real flash_erase function (VA 0x132D6).
P4b INIT 0x04EA addi a5,a5,-0x1C4 → addi a5,a5,+0x266
Same fallback path — redirects the iface[0x0C] store to the real
flash_write function (VA 0x13266).
P5 ERASE 0x0C40 c.beqz a5,+70 → c.nop
A flag check (iface[0x18].bit3) gates the DMA erase path. The flag
is never set on this chip variant so the handler always branched to
a no-op. This patch makes it always take the DMA path.
P6 ERASE 0x0C42 lw a5,8(s4) → lw a5,0x10(s4)
The handler was loading iface[8] (flash_read pointer) into a5 and
calling it as if it were flash_erase. Changed load offset from 8 to
0x10 so it loads iface[0x10] (flash_erase pointer) instead.
P7 WRITE 0x0BB8 c.beqz a5,+14 → c.nop
A bit1 guard inside the per-block success path skipped the
flash_write call when the flag was clear. The flag is never set by
INIT on this chip. Removed so flash_write is always called.
P8 ERASE 0x0C4E addi a0,s8,0x2B8 → addi a0,zero,0xD8
The handler passed a buffer pointer as a0 to flash_erase. The low
byte (0xB8) is not a valid SPI opcode so the flash ignored every
erase command. Replaced with the correct 64K block-erase opcode
(0xD8).
"""
b = bytearray(blob_bytes)
def patch(off, expected, replacement, name):
exp = bytes(expected)
assert b[off:off+len(exp)] == exp, (
f'CRWN blob {name}: expected {exp.hex()} at {off:#06x}, '
f'got {b[off:off+len(exp)].hex()}')
b[off:off+len(replacement)] = bytes(replacement)
patch(0x0b3c, [0x63,0xee,0x2a,0x01], [0x13,0x00,0x00,0x00], 'P3')
patch(0x04d6, [0x93,0x87,0xc7,0xe3], [0x93,0x87,0x67,0x2d], 'P4a')
patch(0x04ea, [0x93,0x87,0xc7,0xe3], [0x93,0x87,0x67,0x26], 'P4b')
patch(0x0c40, [0xb9,0xc3], [0x01,0x00], 'P5')
patch(0x0c42, [0x83,0x27,0x8a,0x00], [0x83,0x27,0x0a,0x01], 'P6')
patch(0x0bb8, [0x99,0xc7], [0x01,0x00], 'P7')
patch(0x0c4e, [0x13,0x05,0x8c,0x2b], [0x13,0x05,0x80,0x0d], 'P8')
return bytes(b)
def do_the_stuff(execcmd, blocksize, iface):
# -------------------------------------------------------------------------
# Bootloader handshake
# -------------------------------------------------------------------------
resp = execcmd(make_cb(BlCmd.GET_INFO, arg1=0x5259414E, arg3=0x67ca), recv=24)
chipid, loadaddr, commskey, _ = struct.unpack('>12sIII', resp)
print(f' Chip ID: {chipid}')
print(f' Load address: ${loadaddr:08X}')
print(f' Init. commkey: ${commskey:08X}')
resp = execcmd(make_cb(BlCmd.AUTHORIZE, arg1=ab_calckey(commskey)), recv=4)
commskey, = struct.unpack('>I', resp)
print(f' New commkey: ${commskey:08X}')
if iface == 'uart' and args.baud != args.init_baud:
print(f'Changing baudrate to {args.baud} baud...')
execcmd(make_cb(BlCmd.IFACE_PARAM, arg2=0xf0), recv=2)
execcmd(make_cb(BlCmd.IFACE_PARAM, arg1=args.baud, arg2=0x02),
recv=2, switch_baud=args.baud)
# -------------------------------------------------------------------------
# Load and patch blob
# -------------------------------------------------------------------------
is_crwn = b'CRWN' in chipid
is_epic = b'EPIC' in chipid
if is_crwn:
Cmd = NitDlCmdCRWN
blob_data = patch_crwn_blob(dl_blob_crwn)
print('[*] Using CRWN agent blob')
elif is_epic:
# BIOS: EPIC works with CRWN blob
Cmd = NitDlCmdCRWN
blob_data = patch_crwn_blob(dl_blob_crwn)
print(f'[*] Using CRWN agent blob for EPIC ({len(blob_data)} bytes)')
else:
Cmd = NitDlCmd
blob_data = dl_blob
print('[*] Using PRAO agent blob')
data = bytearray(blob_data) + b'\x00' * align_by(len(blob_data), blocksize)
# PRAO template needs header patch; CRWN/EPIC blobs are pre-built
if not is_crwn and not is_epic:
struct.pack_into('<12s4sI', data, 4, chipid, iface.encode(), blocksize)
execcmd(make_cb(BlCmd.MEM_WRITE, arg1=loadaddr,
arg3=(len(data) // blocksize)), send=data)
execcmd(make_cb(BlCmd.SET_CMD_HANDLER, arg1=loadaddr))
# -------------------------------------------------------------------------
# Blob init — detect flash, print chip info
# -------------------------------------------------------------------------
if is_crwn or is_epic:
init_resp = execcmd(make_cb(Cmd.INIT, arg1=0), recv=4)
density = init_resp[2] or init_resp[1]
fsize = (1 << (density + 6)) if density else None
# Codekey is derived by INIT from flash[0x1FC..0x1FD] using:
# val = uint16_LE( flash[0x1FC] )
# codekey = (~val & 0xFFFF) ^ (val << 16) ^ 0x594B5048
# This mirrors what the chip writes to SFR 0x348 (flash DMA key).
raw_ck = execcmd(make_cb(Cmd.DEV_READ, arg1=0x1fc, arg3=1), recv=512)
val = struct.unpack_from('<H', raw_ck, 0)[0]
codekey = ((~val & 0xffff) ^ (val << 16) ^ 0x594b5048) & 0xffffffff
# CMD 0x25 reads the JEDEC device ID via ROM 0x80064 (opcode 0x9F).
# The blob returns it big-endian in response bytes [0xC..0xF].
# Bytes [0x0..0x7] of the response buffer may contain the 64-bit Unique
# ID (opcode 0x4B) if the AB530x ROM fills it — this depends on the ROM
# revision. We extract it opportunistically and skip if all-zero.
try:
resp25 = execcmd(make_cb(Cmd.GET_INFO), recv=16)
flash_id = struct.unpack_from('>I', resp25, 0xc)[0] & 0xffffff
flash_uid_raw = resp25[0:8]
flash_uid = flash_uid_raw if any(b != 0 for b in flash_uid_raw) else None
except Exception:
flash_id = 0
flash_uid = None
print(f'- Code key: >>>> {codekey:08X} <<<<')
if flash_id:
print(f'- Flash device ID: {flash_id:06X}')
if flash_uid:
print(f'- Flash unique ID: {flash_uid.hex()}')
if fsize:
print(f'- Flash size: {fsize} bytes')
else:
print('- Flash size: unknown')
else:
codekey, flashid, flashuid = struct.unpack(
'II16s', execcmd(make_cb(Cmd.INIT), recv=48))
density = flashid & 0xff
fsize = (1 << density) if 0x10 <= density <= 0x18 else None
print(f'- Code key: >>>> {codekey:08X} <<<<')
print(f'- Flash device ID: {flashid:06X}')
print(f'- Flash unique ID: {flashuid.hex()}')
if fsize:
print(f'- Flash size: {fsize} bytes')
else:
print('- Flash size: unknown')
# -------------------------------------------------------------------------
# Flash operations
# -------------------------------------------------------------------------
def do_dev_erase(addr, size):
"""Erase a flash region, choosing 64K or 4K blocks automatically."""
saddr = addr & ~0xFFF
eaddr = (addr + size + 0xFFF) & ~0xFFF
tq = tqdm(desc='Erasing', total=(eaddr - saddr),
unit='B', unit_divisor=1024, unit_scale=True)
try:
cur = saddr
while cur < eaddr:
aligned_64k = (cur & 0xFFFF) == 0
space_64k = (eaddr - cur) >= 0x10000
if is_crwn or is_epic:
# CMD 0x2F / arg3=1: blob sends one SPI erase command (0xD8,
# 64K block erase) via flash_erase and returns immediately —
# without polling BUSY. We then issue a DEV_READ (0x28) for
# one 512-byte block: the ROM flash_read driver waits for the
# flash BUSY bit (WIP, STATUS[0]) to clear before asserting
# CS and sending the SPI READ command, so this dummy read
# naturally blocks until the erase is complete.
execcmd(make_cb(Cmd.DEV_ERASE, arg1=cur,
arg2=0x01, arg3=1), recv=512)
execcmd(make_cb(Cmd.DEV_READ, arg1=0, arg3=1), recv=512)
cur += 0x10000
tq.update(0x10000)
else:
if aligned_64k and space_64k:
blksize, flags = 0x10000, 0x00
else:
blksize, flags = 0x1000, 0x02
execcmd(make_cb(Cmd.DEV_ERASE, arg1=cur, arg2=flags))
cur += blksize
tq.update(blksize)
finally:
tq.close()
# ------------------------------------------------------------------
try:
if args.action == 'erase':
for i in range(0, len(args.areas), 2):
addr = int(args.areas[i], 0)
size = int(args.areas[i + 1], 0)
if size <= 0:
if fsize is None: raise RuntimeError('Unknown flash size')
size = fsize - addr
do_dev_erase(addr, size)
elif args.action == 'read':
for i in range(0, len(args.areas), 3):
addr = int(args.areas[i], 0)
size = int(args.areas[i + 1], 0)
path = args.areas[i + 2]
if size <= 0:
if fsize is None: raise RuntimeError('Unknown flash size')
size = fsize - addr
io_size = min(0x8000, max(blocksize,
align_to(size // 100, blocksize)))
print(f'Reading {size} bytes from @{addr:06X} into "{path}"...')
tq = tqdm(desc='Reading', total=size,
unit='B', unit_divisor=1024, unit_scale=True)
try:
with open(path, 'wb') as f:
done = 0
while done < size:
n = min(io_size, size - done)
_a3 = (n // 512) if (is_crwn or is_epic) else n
f.write(execcmd(
make_cb(Cmd.DEV_READ, arg1=addr + done, arg3=_a3),
recv=n))
tq.update(n)
done += n
finally:
tq.close()
elif args.action == 'write':
for i in range(0, len(args.areas), 2):
addr = int(args.areas[i], 0)
path = args.areas[i + 1]
with open(path, 'rb') as f:
raw = f.read()
BLOCK = 512
if len(raw) % BLOCK:
raw += b'\xff' * (BLOCK - len(raw) % BLOCK)
n_blocks = len(raw) // BLOCK
print(f'Writing {len(raw)} bytes to @{addr:06X} from "{path}"...')
do_dev_erase(addr, len(raw))
if is_crwn or is_epic:
# CMD 0x2A — CRWN write, one 512-byte block per call.
#
# Flow for each block:
# send_packet(CB) — BL ACKs immediately, then calls blob.
# Blob enters ROM recv() and waits for
# the data packet.
# send_packet(data) — BL's UART ISR independently receives
# this framed 512-byte packet, ACKs it,
# and stores the payload in the buffer
# pointed to by s0 (= iface[4], the CB
# pointer set at blob entry). ROM recv()
# reads from that same buffer and returns
# 512.
# recv=1 — drains the 1-byte blob response, keeps
# BL's TX queue empty and the packet
# counter in sync for the next block.
#
# arg2=0x00 keeps a3=0 inside the blob so flash_write programs
# flash with DMA key=0 (no encryption). arg2=0x02 would load
# iface[4] as the DMA key and silently corrupt every byte.
tq = tqdm(desc='Writing', total=len(raw),
unit='B', unit_divisor=1024, unit_scale=True)
try:
for blk in range(n_blocks):
cb = make_cb(Cmd.DEV_WRITE,
arg1=addr + blk * BLOCK,
arg2=0x00, arg3=1)
execcmd(cb,
send=raw[blk * BLOCK : (blk + 1) * BLOCK],
recv=1)
tq.update(BLOCK)
finally:
tq.close()
else:
io_size = min(0x8000, max(blocksize,
align_to(len(raw) // 100, blocksize)))
tq = tqdm(desc='Writing', total=len(raw),
unit='B', unit_divisor=1024, unit_scale=True)
try:
done = 0
while done < len(raw):
block = raw[done:done + io_size]
execcmd(make_cb(Cmd.DEV_WRITE,
arg1=addr + done, arg3=len(block)),
send=block)
tq.update(len(block))
done += len(block)
finally:
tq.close()
except KeyboardInterrupt:
print('interrupted!')
except Exception as e:
print('failed:', e)
raise
if args.reboot:
execcmd(make_cb(BlCmd.REBOOT))
###############################################################################
if have_uart and args.port is not None:
with Serial(args.port) as port:
# Detect whether the adapter has a TX→RX loopback (e.g. CH340 with
# 200 Ω resistor) or not (e.g. Bluetrum's original half-duplex dongle).
#
# Half-duplex dongle (no loopback):
# port_write sends bytes to chip, chip replies → those replies are
# read back as the "echo". has_echo=True is correct.
#
# Full-duplex adapter + resistor (loopback present):
# Our own TX bytes come straight back on RX before the chip replies.
# port_write must NOT try to consume them; the real chip reply is
# read later by send_packet/_recv_token_packet. has_echo=False.
#
# Detection: send one byte at low baud with a short timeout.
# If it echoes back → loopback adapter → has_echo=False.
# If nothing comes back → half-duplex dongle → has_echo=True.
if args.no_echo:
has_echo = False
else:
# Detect TX→RX loopback.
#
# CH340 (standard driver + 200Ω resistor): TX bytes loop back on
# RX immediately (within ~1 byte-time = 87µs at 115200).
# has_echo=True so port_write consumes them before reading the
# real chip response.
#
# Original Bluetrum dongle (AB5305A / CP2102 modified driver):
# driver suppresses local echo → TX bytes do NOT return on RX.
# has_echo=False so port_write doesn't try to read phantom bytes.
#
# Detection: send 0xFF (ignored by BL sync state machine) with a
# 5 ms read timeout — long enough for loopback (arrives in <1 ms)
# but shorter than the minimum chip processing + reply time.
port.baudrate = args.init_baud
port.timeout = 0.005
port.write(b'\xff')
loopback = port.read(1) == b'\xff'
port.reset_input_buffer()
port.timeout = 0.01
has_echo = loopback
udl = UARTDownload(port, has_echo=has_echo)
print('Trying to synchronize.', end='')
port.timeout = .01
try:
done = False
num = 0
turn = 0
while not done:
if num < 10:
udl.port.reset_input_buffer()
udl.port.write(UARTDownload.SYNC_TOKEN)
while not done:
recv = udl.port.read(4)
if recv == b'': break
if recv == UARTDownload.SYNC_RESP:
done = True
num += 1
else:
print('.', end='', flush=True)
if turn == 0:
udl.port.baudrate = args.init_baud
udl.send_reset(True)
turn = 1
else:
udl.port.baudrate = args.baud
udl.send_reset(True)
udl.port.baudrate = args.init_baud
turn = 0
num = 0
except Exception as e:
print(' failed:')
raise e
else:
print(' done.')
port.reset_input_buffer()
port.timeout = .1
def execcmd(cb, send=None, recv=None, max_io=512, switch_baud=None):
udl.send_packet(cb)
if switch_baud is not None:
port.baudrate = switch_baud
if send is not None:
sent = 0
while sent < len(send):
n = min(len(send) - sent, max_io)
udl.send_packet(send[sent:sent + n])
sent += n
elif recv is not None:
data = b''
while len(data) < recv:
n = min(recv - len(data), max_io)
block = udl.recv_packet()
data += block
if len(block) != n:
break
return data
do_the_stuff(execcmd, 512, 'uart')
elif have_scsi and args.mscdev is not None:
with SCSIDev(args.mscdev) as dev:
def execcmd(cb, send=None, recv=None, **_):
if recv is not None:
recv = bytearray(recv)
if send is not None and not isinstance(send, bytes):
send = bytes(send)
dev.execute(b'\xfc' + cb, send, recv)
return recv
do_the_stuff(execcmd, 512, 'usb')
else:
print('No device specified:')
if have_uart: print(' - UART: specify the serial port via --port')
if have_scsi: print(' - USB MSC: specify the device via --mscdev')Скрипт для разделения областей которые бьются конкретно у меня:
r=open('dump.bin','rb').read()
open('fix_10000.bin','wb').write(r[0x10000:0x20000])
open('fix_30000.bin','wb').write(r[0x30000:0x40000])
open('fix_50000.bin','wb').write(r[0x50000:0x60000])
print('ok')Ну и пишем их в чип:
download.py --port COM8 write 0x10000 fix_10000.bin download.py --port COM8 write 0x30000 fix_30000.bin download.py --port COM8 write 0x50000 fix_50000.bin
Так что при записи лучше перепроверять и перезаписывать битые области.
Или не та ветка в блобе, или не та последоватоельность. Ну и скорость. И флаги.
USMicro® 2026©
А, кажись догнал - не та ветка в блобе, без ACK/NACK части, снизить скорость уарта - должно не пропускать.
USMicro® 2026©
Вы здесь » USMI » MCU, SoC, CPU Микроконтроллеры » Bluetrum SoC