DMR-чип опознан по схеме: HF6853 = AUCTUS A6 (RDA8809), не SCT3258
Опознание БЕЗ разбора рации, по схеме RT4DDLT01 (Radtel v2.1): DMR-модуль «FM100B» = один чип U700 HF6853 — одночиповый DMR-SoC семейства Auctus A6 (внутри RDA8809): CPU + ВЧ-трансивер + DSP + AMBE-вокодер + аудио-кодек в одном кристалле, прошивка во внешнем SPI-flash, кварц 26 МГц. Прежняя версия «Sicomm SCT3258TD» неверна (SCT3258 — baseband-only без ВЧ; и нативный протокол другой). Скрытый ATE/CPS-интерфейс полностью реверснут (jhart99/a6tools, вендорен в research/a6tools). Даёт КОНКРЕТНУЮ починку нашей TX-проблемы: AT+GETFREQERR / AT+DMOFREQERR=N, offset=-2500+10*N, ±2500 Гц — наши ~1100 Гц внутри диапазона. Плюс DMR_ADJTXSYMDEV (девиация), FGU_AFC, DMOSETPOWER, CPS chanInfo, дамп прошивки модуля. - docs/hf6853-auctus-a6.md — идентификация + протокол + план TX-fix - tools/a6_freqfix.py — заготовка тула (probe/read/write, не запускать вслепую) - research/a6tools/ — вендоренный реверс (MIT), atcommands.md/cpecommands.md - docs/dmr-tx-recovery.md, README — путь 0 (AT+DMOFREQERR) как лучший - research/rt4ddlt01-dmr-module-sheet.png — рендер листа схемы DMR-модуля Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NikMGoqQWWk9wy2ww2vJAr
Этот коммит содержится в:
@@ -0,0 +1,168 @@
|
||||
from .rdadebug import write_register_int8
|
||||
from .rdadebug import read_register_int8
|
||||
from .rdadebug import write_block
|
||||
from .rdadebug import compute_check
|
||||
from .eprint import eprint
|
||||
|
||||
def h2p_command(msg):
|
||||
""" Format a frame for an h2p command
|
||||
|
||||
The CPS software sends commands to a special debug register
|
||||
00000005. Writing a value to this register throws an interupt
|
||||
which is picked up by a function on the device.
|
||||
|
||||
0x00 : Command finished, clears semaphore
|
||||
0xA5 : Process command with RxByHostPortCB
|
||||
0xEE : Reboot
|
||||
0xFF : Handle with boot_HstCmdBasicHandler
|
||||
|
||||
@param msg: the message to send
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(0x5, msg)
|
||||
|
||||
def set_uart_to_normal():
|
||||
""" Set device uart to host mode
|
||||
|
||||
The CPS software sends repeated requests to set internal register
|
||||
00000003 to 0x80 which has the effect of locking the UART to debug
|
||||
mode
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(3, 0x00)
|
||||
|
||||
def set_uart_to_host():
|
||||
""" Set device uart to host mode
|
||||
|
||||
The CPS software sends repeated requests to set internal register
|
||||
00000003 to 0x80 which has the effect of locking the UART to debug
|
||||
mode
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(3, 0x80)
|
||||
|
||||
def reboot_and_freeze():
|
||||
""" Reboot and freeze the processor
|
||||
|
||||
This command comes from coolwatcher and resets the processor and
|
||||
immediately halts it. This is useful for stepping through the
|
||||
boot process, but also allows some areas of ROM to be read without
|
||||
crashing
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return write_register_int8(0, 0x03)
|
||||
|
||||
def read_uart_to_host():
|
||||
""" make a frame containing a knock command
|
||||
|
||||
this function creates a frame that I assume wakes up the device
|
||||
for further commands.
|
||||
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
return read_register_int8(3)
|
||||
|
||||
def ate_command(cmd, p_atecps_write):
|
||||
""" make a frame containing an ATE command
|
||||
|
||||
@param cmd: the command to send
|
||||
@param p_atecps_write: the address of the CPS write register
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
cmd = bytearray(cmd, 'utf-8') + b'\r'
|
||||
cmd += bytes(4 - len(cmd) % 4)
|
||||
return write_block(p_atecps_write, cmd)
|
||||
|
||||
def cps_command(cmd, p_atecps_write):
|
||||
""" make a frame containing an CPS command
|
||||
|
||||
@param cmd: the command to send
|
||||
@param p_atecps_write: the address of the CPS write register
|
||||
@return: the frame to send
|
||||
|
||||
"""
|
||||
length = (len(cmd) + 4).to_bytes(1, 'big')
|
||||
check = compute_check(length + cmd)
|
||||
begin = bytes([0xaa])
|
||||
end = bytes([0xbb])
|
||||
msg = begin + length + cmd + check + end
|
||||
padding = 4 - (len(msg) % 4)
|
||||
return write_block(p_atecps_write, msg + bytes([0x00]) * padding)
|
||||
|
||||
class CPSFrame:
|
||||
""" Received CPS class
|
||||
|
||||
This class decodes CPS frames received from the device.
|
||||
"""
|
||||
check_fail = False
|
||||
length = 0
|
||||
type = 0
|
||||
content = bytes([])
|
||||
def __init__(self, msg):
|
||||
eprint(msg.hex())
|
||||
if (msg[-1].to_bytes(1, 'big') != compute_check(msg[1:-2])):
|
||||
self.check_fail = True
|
||||
eprint('CPS frame check failed')
|
||||
return
|
||||
self.length = msg[1]
|
||||
self.type = int.from_bytes(msg[2:4], 'big')
|
||||
self.is_ok = msg[4] == 0x01
|
||||
self.content = msg[5:-3]
|
||||
def __repr__(self):
|
||||
return 'packet length {} type {} is_ok {} content {}'.format(self.length, self.type, self.is_ok, self.content)
|
||||
|
||||
class ChanInfoFrame(CPSFrame):
|
||||
""" Received ChanInfoFrame class
|
||||
|
||||
This class decodes ChanInfoFrame frames received from the device.
|
||||
|
||||
"\tcpsInst.chanInfo.nChanIndex=%d\n
|
||||
\tcpsInst.chanInfo.nChanType=%d\n
|
||||
\tcpsInst.chanInfo.nVox=%d\n
|
||||
\tcpsInst.chanInfo.nPower=%d\n
|
||||
\tcpsInst.chanInfo.nRxFreq=%d\n
|
||||
\tcpsInst.chanInfo.nTxFreq=%d\n
|
||||
\tcpsInst.chanInfo.nTxContactsIdx=0x%08x\n
|
||||
\tcpsInst.chanInfo.nColorCode=%d\n
|
||||
\tcpsInst.chanInfo.nTimeSlot=%d\n
|
||||
\tcpsInst.chanInfo.bPoliteCall=%d\n"
|
||||
\tcpsInst.chanInfo.nEmrSys=%d\n
|
||||
\tcpsInst.chanInfo.nEncry=%d\n
|
||||
\tcpsInst.chanInfo.nTypeWideNarrow=%d\n
|
||||
\tcpsInst.chanInfo.nRxCtdcs=%d\n
|
||||
\tcpsInst.chanInfo.bRxCtdcsInvert=%d\n
|
||||
\tcpsInst.chanInfo.bTxCtdcsInvert=%d\n
|
||||
\tcpsInst.chanInfo.nTxCtdcs=%d\n
|
||||
\tcpsInst.chanInfo.nRxGrpListIdx=%d\n"
|
||||
"""
|
||||
def __init__(self, msg):
|
||||
super().__init__(msg)
|
||||
self.index = int.from_bytes(self.content[0:2], 'little')
|
||||
self.chantype = self.content[2]
|
||||
self.rxFreq = int.from_bytes(self.content[4:8], 'little')
|
||||
self.txFreq = int.from_bytes(self.content[8:12], 'little')
|
||||
self.txContactIndex = int.from_bytes(self.content[12:16], 'little')
|
||||
self.colorCode = self.content[16]
|
||||
self.timeslot = self.content[17]
|
||||
self.polite = self.content[18]
|
||||
self.emrSys = int.from_bytes(self.content[1:2], 'big')
|
||||
self.encryption = int.from_bytes(self.content[1:2], 'big')
|
||||
self.widenarrow = int.from_bytes(self.content[1:2], 'big')
|
||||
self.rxctdcs = int.from_bytes(self.content[1:2], 'big')
|
||||
self.rxctdcsinvert = int.from_bytes(self.content[1:2], 'big')
|
||||
self.txctdcsinvert = int.from_bytes(self.content[1:2], 'big')
|
||||
self.txctdcs = int.from_bytes(self.content[1:2], 'big')
|
||||
self.rxGroupIdx = int.from_bytes(self.content[1:2], 'big')
|
||||
self.vox = int.from_bytes(self.content[1:2], 'big')
|
||||
def __repr__(self):
|
||||
return 'packet length {} type {} is_ok {} index {} chantype {} rxfreq {} txfreq {}'.format(
|
||||
self.length, self.type, self.is_ok, self.index, self.chantype, self.rxFreq, self.txFreq)
|
||||
Ссылка в новой задаче
Block a user