FM100B = Sicomm SCT3258TD (документирован); Talker Alias, агент, SPI-инструменты, восстановление TX
Главное: - Опознан DMR-чип: FM100B = Sicomm SCT3258TD. Датащит Packet Interface (126 стр) в docs/. Открывает калибровку/AMBE/вокодер по документации. Родня: UV-4R, китайские клоны. Реализовано: - Приём Talker Alias (cmd 0x60) — позывной крупно на экране (build_ta.py) - Интерактивный агент — управление FM100B по COM без перешивки (build_agent.py + agent.py) - Полная карта команд FM100B с живой рации (fm100b-commands.md) - Работа с SPI: чтение/запись/восстановление codeplug и калибровки (spi_read/spi_wr/spi_restore) - Конструктор дисплея + редактор прошивки (tools/display-editor) Документация: - docs/SCT3258_packet_interface.txt/.pdf — спека DMR-чипа - docs/fm100b-commands.md — карта команд - docs/dmr-tx-recovery.md — диагноз/восстановление DMR-передачи Инструменты: flash_ps.py (прошивка через WSL/powershell), sniff.py (снифер кадров), hotspot_rxoffset_sweep.py, apply_patch.py, build_* сборщики. Важно: DMR TX-калибровка сбита в экспериментах со SET-командами модулю; восстановление через offset-компенсацию / .updat Radtel / калибровку по прибору (docs/dmr-tx-recovery.md).
Этот коммит содержится в:
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Напряжение батареи вместо штатной иконки + уборка полосы у всплывающего окна.
|
||||
|
||||
Патчи:
|
||||
1. Свой код в свободном месте после конца образа (0x08029000):
|
||||
stub — пустышка `bx lr`
|
||||
batt_draw — рисует "8.4V" мелким шрифтом 5x8 на странице 0
|
||||
tick_router — вызывает оригинал, затем batt_draw (только на главном экране)
|
||||
2. `bl 0x08008300` @0x080148E2 — отрисовка штатной шкалы -> stub (иконки больше нет).
|
||||
3. `bl 0x08010A00` @0x0801E2A4 — «напряжение -> индекс 0..3» убрано (2 x nop),
|
||||
чтобы флаг перерисовки взводился на каждые 0.1 В, а не только на порогах.
|
||||
4. `bl 0x0800842C` @0x080085DE — верхняя полоса рамки всплывающего окна убрана.
|
||||
5. `bl 0x0801DFD0` @0x08020806 — последний вызов цепочки отрисовки в ui_tick_normal
|
||||
перенаправлен на tick_router.
|
||||
|
||||
Почему через конец цикла: раньше мы рисовали из середины кадра (из цепочки обновления
|
||||
батареи), и любой более поздний вывод затирал цифры — при входящем вызове «7» теряла
|
||||
перекладину и читалась как «1». Теперь наш текст ложится последним и перекрывает всё.
|
||||
"""
|
||||
import struct
|
||||
from keystone import Ks, KS_ARCH_ARM, KS_MODE_THUMB
|
||||
|
||||
BASE = 0x08002800
|
||||
SRC = "stock-fw/rt4d_ru_full.bin"
|
||||
OUT = "stock-fw/rt4d_ru_batt.bin"
|
||||
|
||||
SMALL_CHAR = 0x080089AC # void small_char(u8 page, u8 x, u8 ch, u8 dim)
|
||||
BATT_VOLT = 0x200008B0 # u16, напряжение * 10
|
||||
G_SCREEN = 0x200008B3 # u8, текущий экран (0 = главный)
|
||||
|
||||
GAUGE_CALL = 0x080148E2 # bl 0x08008300 — штатная шкала батареи
|
||||
GAUGE_FUNC = 0x08008300
|
||||
LEVEL_CALL = 0x0801E2A4 # bl 0x08010A00 — напряжение -> индекс
|
||||
LEVEL_FUNC = 0x08010A00
|
||||
TOPBAR_CALL = 0x080085DE # bl fill_col_run(page=1) — верхняя полоса рамки
|
||||
FILL_RUN = 0x0800842C
|
||||
TICK_CALL = 0x08020806 # bl 0x0801DFD0 — последний вызов отрисовки за кадр
|
||||
TICK_FUNC = 0x0801DFD0
|
||||
|
||||
MYCODE = 0x08029000
|
||||
X0, X1, X2, X3 = 108, 113, 118, 123 # позиции четырёх символов
|
||||
|
||||
|
||||
def thumb_bl(src: int, dst: int) -> bytes:
|
||||
off = dst - (src + 4)
|
||||
assert -(1 << 24) <= off < (1 << 24), "цель BL слишком далеко"
|
||||
off >>= 1
|
||||
s = (off >> 23) & 1
|
||||
i1 = (off >> 22) & 1
|
||||
i2 = (off >> 21) & 1
|
||||
imm10 = (off >> 11) & 0x3FF
|
||||
imm11 = off & 0x7FF
|
||||
j1 = (~i1 ^ s) & 1
|
||||
j2 = (~i2 ^ s) & 1
|
||||
return struct.pack("<HH", 0xF000 | (s << 10) | imm10,
|
||||
0xD000 | (j1 << 13) | (j2 << 11) | imm11)
|
||||
|
||||
|
||||
def main():
|
||||
img = bytearray(open(SRC, "rb").read())
|
||||
print(f"исходный образ: {len(img)} Б, конец на 0x{BASE + len(img) - 1:08X}")
|
||||
assert BASE + len(img) == MYCODE, "образ не заканчивается там, где ждём код"
|
||||
ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB)
|
||||
|
||||
def check(addr, target, what):
|
||||
o = addr - BASE
|
||||
cur = bytes(img[o:o + 4])
|
||||
assert cur == thumb_bl(addr, target), \
|
||||
f"по 0x{addr:08X} ({what}) не тот вызов: {cur.hex(' ')}"
|
||||
return o
|
||||
|
||||
o_gauge = check(GAUGE_CALL, GAUGE_FUNC, "шкала батареи")
|
||||
o_level = check(LEVEL_CALL, LEVEL_FUNC, "напряжение->индекс")
|
||||
o_bar = check(TOPBAR_CALL, FILL_RUN, "верхняя полоса рамки")
|
||||
o_tick = check(TICK_CALL, TICK_FUNC, "хвост отрисовки")
|
||||
print("все четыре точки патча подтверждены")
|
||||
|
||||
# ---- раскладка своего кода ----
|
||||
stub_addr = MYCODE
|
||||
stub = bytes(ks.asm("bx lr", stub_addr)[0])
|
||||
|
||||
batt_addr = stub_addr + len(stub)
|
||||
batt_asm = f"""
|
||||
push {{r4, r5, r6, lr}}
|
||||
movw r4, #{BATT_VOLT & 0xFFFF}
|
||||
movt r4, #{(BATT_VOLT >> 16) & 0xFFFF}
|
||||
ldrh r4, [r4]
|
||||
uxtb r4, r4
|
||||
movs r1, #205
|
||||
muls r1, r4, r1
|
||||
lsrs r5, r1, #11
|
||||
movs r1, #10
|
||||
muls r1, r5, r1
|
||||
subs r4, r4, r1
|
||||
movs r0, #0
|
||||
movs r1, #{X0}
|
||||
mov r2, r5
|
||||
add r2, #0x30
|
||||
movs r3, #0
|
||||
bl #{SMALL_CHAR}
|
||||
movs r0, #0
|
||||
movs r1, #{X1}
|
||||
movs r2, #0x2E
|
||||
movs r3, #0
|
||||
bl #{SMALL_CHAR}
|
||||
movs r0, #0
|
||||
movs r1, #{X2}
|
||||
mov r2, r4
|
||||
add r2, #0x30
|
||||
movs r3, #0
|
||||
bl #{SMALL_CHAR}
|
||||
movs r0, #0
|
||||
movs r1, #{X3}
|
||||
movs r2, #0x56
|
||||
movs r3, #0
|
||||
bl #{SMALL_CHAR}
|
||||
pop {{r4, r5, r6, pc}}
|
||||
"""
|
||||
batt = bytes(ks.asm(batt_asm, batt_addr)[0])
|
||||
|
||||
# tick_router: оригинал -> проверка экрана -> batt_draw.
|
||||
# keystone не умеет метки, поэтому условный переход кодируем вручную.
|
||||
tick_addr = batt_addr + len(batt)
|
||||
partA = bytes(ks.asm(f"""
|
||||
push {{r4, lr}}
|
||||
bl #{TICK_FUNC}
|
||||
movw r4, #{G_SCREEN & 0xFFFF}
|
||||
movt r4, #{(G_SCREEN >> 16) & 0xFFFF}
|
||||
ldrb r4, [r4]
|
||||
cmp r4, #0
|
||||
""", tick_addr)[0])
|
||||
bne_addr = tick_addr + len(partA)
|
||||
partB = bytes(ks.asm(f"bl #{batt_addr}", bne_addr + 2)[0])
|
||||
bne = struct.pack("<H", 0xD100 | (((len(partB) - 2) // 2) & 0xFF)) # bne -> за partB
|
||||
partC = bytes(ks.asm("pop {r4, pc}", bne_addr + 2 + len(partB))[0])
|
||||
tick = partA + bne + partB + partC
|
||||
|
||||
code = stub + batt + tick
|
||||
print(f"свой код: {len(code)} Б (stub 0x{stub_addr:08X}, "
|
||||
f"batt_draw 0x{batt_addr:08X}, tick_router 0x{tick_addr:08X})")
|
||||
|
||||
img += code
|
||||
while len(img) % 4:
|
||||
img.append(0xFF)
|
||||
|
||||
# ---- патчи ----
|
||||
img[o_gauge:o_gauge + 4] = thumb_bl(GAUGE_CALL, stub_addr)
|
||||
print("штатная шкала батареи отключена")
|
||||
img[o_level:o_level + 4] = b"\x00\xbf\x00\xbf"
|
||||
print("преобразование в индекс убрано — перерисовка на каждые 0.1 В")
|
||||
img[o_bar:o_bar + 4] = b"\x00\xbf\x00\xbf"
|
||||
print("верхняя полоса рамки убрана")
|
||||
img[o_tick:o_tick + 4] = thumb_bl(TICK_CALL, tick_addr)
|
||||
print("вывод напряжения перевешен на конец кадра")
|
||||
|
||||
open(OUT, "wb").write(bytes(img))
|
||||
print(f"готово: {OUT}, {len(img)} Б")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Ссылка в новой задаче
Block a user