feat(amp): BLE SysEx R/W for AD85050 CH2 EQ RAM and I2C scan

Expose 0xA0-0xAE via 07 00/01 nibble-coded frames, add RAM helpers, and host scan/test scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yuquanjun 2026-09-24 06:14:56 +08:00
parent c480e96a3f
commit 3b0f5479ba
10 changed files with 569 additions and 2 deletions

View File

@ -727,6 +727,27 @@ uint8_t app_log_try_command(const char *cmd)
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "FLASH_ERASE_OK\n");
return 1U;
}
if (strncmp(cmd, "i2c scan", 8) == 0) {
uint8_t found[16];
uint8_t n;
uint8_t i;
char line[48];
n = wk_i2c1_scan(found, (uint8_t)sizeof(found));
for (i = 0U; i < n && i < (uint8_t)sizeof(found); i++) {
if (found[i] == 0x30U) {
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "I2C ACK 0x30 AD85050\n");
} else if (found[i] == 0x5AU) {
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "I2C ACK 0x5A AW816\n");
} else {
snprintf(line, sizeof(line), "I2C ACK 0x%02X\n", (unsigned)found[i]);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
}
}
snprintf(line, sizeof(line), "I2C_SCAN_END n=%u\n", (unsigned)n);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
return 1U;
}
if (strncmp(cmd, "ui boot", 7) == 0) {
/* 先 ACK:全屏 Logo 绘制很慢,避免 HIL/主机超时误判 */
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "UI_BOOT_OK\n");

View File

@ -231,6 +231,70 @@ AD85020B_StatusTypeDef AD85020B_I2C_Read(uint8_t reg, uint8_t *data)
return AD85020B_I2C_ReadImpl(reg, data);
}
static AD85020B_StatusTypeDef AD85020B_RamWaitClear(uint8_t mask)
{
uint8_t i;
uint8_t st;
for (i = 0U; i < 20U; i++)
{
if (AD85020B_I2C_Read(AD85020B_REG_CFRW, &st) != AD85020B_OK)
return AD85020B_ERROR;
if ((st & mask) == 0U)
return AD85020B_OK;
wk_delay_ms(1);
}
return AD85020B_TIMEOUT;
}
/* 手册:CFA 只收 7 位;表中 0xA0~0xAE 与 CH1 同址、靠 RBS 分 bank。
Channel-2 EQ16/17/18 用 bank=1。 */
AD85020B_StatusTypeDef AD85020B_RamWrite24(uint8_t ram_addr, uint8_t bank, uint32_t value)
{
uint8_t cfrw;
if (AD85020B_I2C_Write(AD85020B_REG_CFA, (uint8_t)(ram_addr & 0x7FU)) != AD85020B_OK)
return AD85020B_ERROR;
if (AD85020B_I2C_Write(AD85020B_REG_A1_T, (uint8_t)((value >> 16) & 0xFFU)) != AD85020B_OK)
return AD85020B_ERROR;
if (AD85020B_I2C_Write(AD85020B_REG_A1_M, (uint8_t)((value >> 8) & 0xFFU)) != AD85020B_OK)
return AD85020B_ERROR;
if (AD85020B_I2C_Write(AD85020B_REG_A1_B, (uint8_t)(value & 0xFFU)) != AD85020B_OK)
return AD85020B_ERROR;
cfrw = (uint8_t)(AD85020B_CFRW_W1 | (bank ? AD85020B_CFRW_RBS : 0U));
if (AD85020B_I2C_Write(AD85020B_REG_CFRW, cfrw) != AD85020B_OK)
return AD85020B_ERROR;
return AD85020B_RamWaitClear(AD85020B_CFRW_W1);
}
AD85020B_StatusTypeDef AD85020B_RamRead24(uint8_t ram_addr, uint8_t bank, uint32_t *value)
{
uint8_t cfrw;
uint8_t hi, mid, lo;
if (value == 0)
return AD85020B_ERROR;
if (AD85020B_I2C_Write(AD85020B_REG_CFA, (uint8_t)(ram_addr & 0x7FU)) != AD85020B_OK)
return AD85020B_ERROR;
cfrw = (uint8_t)(AD85020B_CFRW_R1 | (bank ? AD85020B_CFRW_RBS : 0U));
if (AD85020B_I2C_Write(AD85020B_REG_CFRW, cfrw) != AD85020B_OK)
return AD85020B_ERROR;
if (AD85020B_RamWaitClear(AD85020B_CFRW_R1) != AD85020B_OK)
return AD85020B_TIMEOUT;
if (AD85020B_I2C_Read(AD85020B_REG_A1_T, &hi) != AD85020B_OK)
return AD85020B_ERROR;
if (AD85020B_I2C_Read(AD85020B_REG_A1_M, &mid) != AD85020B_OK)
return AD85020B_ERROR;
if (AD85020B_I2C_Read(AD85020B_REG_A1_B, &lo) != AD85020B_OK)
return AD85020B_ERROR;
*value = ((uint32_t)hi << 16) | ((uint32_t)mid << 8) | (uint32_t)lo;
return AD85020B_OK;
}
/**
* @brief AD85020B 初始化
* @retval AD85020B_StatusTypeDef: 初始化结果

View File

@ -40,6 +40,23 @@
#define AD85020B_REG_MVOL 0x03 // 主音量控制
#define AD85020B_REG_HEADPHONEVOL 0x87 // 耳机音量控制
/* 系数 RAM 间接访问(手册 0x1D~0x2D) */
#define AD85020B_REG_CFA 0x1D /* CFA[6:0] RAM 基址 */
#define AD85020B_REG_A1_T 0x1E /* 系数 top [23:16] */
#define AD85020B_REG_A1_M 0x1F /* 系数 middle [15:8] */
#define AD85020B_REG_A1_B 0x20 /* 系数 bottom [7:0] */
#define AD85020B_REG_CFRW 0x2D /* CfRW 读写触发 */
#define AD85020B_CFRW_W1 0x01 /* 写单系数 */
#define AD85020B_CFRW_WA 0x02
#define AD85020B_CFRW_R1 0x04 /* 读单系数 */
#define AD85020B_CFRW_RA 0x08
#define AD85020B_CFRW_W3 0x10
#define AD85020B_CFRW_R3 0x20
#define AD85020B_CFRW_RBS 0x40 /* 0=bank0/CH1, 1=bank1/CH2 */
#define AD85020B_RAM_CH2_EQ16_A1 0xA0 /* CH2EQ16A1 */
#define AD85020B_RAM_CH2_EQ18_A0 0xAE /* CH2EQ18A0 */
/* 软件复位配置:SCTL6寄存器BIT5=SW_RSTB,0=复位,1=正常 */
#define AD85020B_SW_RESET 0x50 // BIT5=0(0x50二进制1010000,BIT5=0)
#define AD85020B_SW_NORMAL 0x70 // BIT5=1(复位后恢复正常)
@ -56,6 +73,8 @@ typedef enum
void AD85020B_I2C_Init(void);
AD85020B_StatusTypeDef AD85020B_I2C_Write(uint8_t reg, uint8_t data);
AD85020B_StatusTypeDef AD85020B_I2C_Read(uint8_t reg, uint8_t *data);
AD85020B_StatusTypeDef AD85020B_RamWrite24(uint8_t ram_addr, uint8_t bank, uint32_t value);
AD85020B_StatusTypeDef AD85020B_RamRead24(uint8_t ram_addr, uint8_t bank, uint32_t *value);
AD85020B_StatusTypeDef AD85020B_Init(void);
#endif

View File

@ -61,7 +61,8 @@ extern "C" {
void wk_i2c1_init(void);
/* add user code begin exported functions */
/* Probe I2C1 7-bit addresses 0x08..0x77 (address-only, no data write). */
uint8_t wk_i2c1_scan(uint8_t *found, uint8_t max_found);
/* add user code end exported functions */
#ifdef __cplusplus

View File

@ -84,5 +84,66 @@ void wk_i2c1_init(void)
}
/* add user code begin 1 */
#include "i2c_application.h"
#define I2C_SCAN_ADDR_MIN 0x08U
#define I2C_SCAN_ADDR_MAX 0x77U
#define I2C_SCAN_TIMEOUT 8000U
extern i2c_status_type i2c_master_write_addr(i2c_handle_type *hi2c, uint16_t address, uint32_t timeout);
static void wk_i2c1_recover(void)
{
i2c_reset(I2C1);
wk_i2c1_init();
}
/* Address-only probe: START + 8-bit write address + STOP. No register write. */
static uint8_t wk_i2c1_probe(uint8_t addr7)
{
i2c_handle_type hi2c;
i2c_status_type status;
uint16_t addr8;
memset(&hi2c, 0, sizeof(hi2c));
hi2c.i2cx = I2C1;
addr8 = (uint16_t)((uint16_t)addr7 << 1);
if (i2c_wait_flag(&hi2c, I2C_BUSYF_FLAG, I2C_EVENT_CHECK_NONE, I2C_SCAN_TIMEOUT) != I2C_OK) {
wk_i2c1_recover();
return 0U;
}
status = i2c_master_write_addr(&hi2c, addr8, I2C_SCAN_TIMEOUT);
if (status == I2C_OK) {
i2c_flag_clear(I2C1, I2C_ADDR7F_FLAG);
i2c_stop_generate(I2C1);
(void)i2c_wait_flag(&hi2c, I2C_BUSYF_FLAG, I2C_EVENT_CHECK_NONE, I2C_SCAN_TIMEOUT);
return 1U;
}
i2c_stop_generate(I2C1);
wk_i2c1_recover();
return 0U;
}
uint8_t wk_i2c1_scan(uint8_t *found, uint8_t max_found)
{
uint8_t n = 0U;
uint8_t addr;
wk_i2c1_init();
for (addr = I2C_SCAN_ADDR_MIN; addr <= I2C_SCAN_ADDR_MAX; addr++) {
if (wk_i2c1_probe(addr) != 0U) {
if (found != 0 && n < max_found) {
found[n] = addr;
}
n++;
}
}
wk_i2c1_init();
return n;
}
/* add user code end 1 */

View File

@ -91,6 +91,9 @@ static void handleSectionB(uint8_t *data);
static void handleSectionC(uint8_t *data);
static void handleSectionD(uint8_t *data);
static void handleAmpRamRead(uint8_t *data);
static void handleAmpRamWrite(uint8_t *data);
@ -149,6 +152,10 @@ static const BLE_SysExCmdItem bleSysExCmdTable[] =
{{0x06, 0x06}, handleSectionB}, /* section B */
{{0x06, 0x07}, handleSectionC}, /* section C */
{{0x06, 0x08}, handleSectionD}, /* section D */
/*======== 7. amp RAM (AD85050 CH2 EQ16/17/18) 0x07 ========*/
{{0x07, 0x00}, handleAmpRamRead}, /* read 24-bit coeff 0xA0..0xAE */
{{0x07, 0x01}, handleAmpRamWrite}, /* write 24-bit coeff 0xA0..0xAE */
};
// ָ<><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
@ -856,6 +863,124 @@ static void handleRhythmStyle(uint8_t *data)
}
}
/*-------- 7. amp RAM 0x07 : AD85050 Channel-2 EQ16/17/18 (0xA0..0xAE) --------
* BLE-MIDI SysEx 数据必须 <0x80,地址/24bit 值用半字节拆开:
* 0xA0 -> 0A 00
* 0x200000 -> 02 00 00 00 00 00
* 读: F0 60 07 00 <regH> <regL> F7
* 回: F0 60 07 00 <regH> <regL> <6 nibbles> F7
* 写: F0 60 07 01 <regH> <regL> <6 nibbles> F7
* 回: 同写帧;失败 F0 60 07 xx <regH> <regL> 7F F7
*/
#define AMP_RAM_BANK_CH2 1U
static uint8_t amp_parse_reg(const uint8_t *data, uint8_t *reg_out)
{
uint8_t hi = data[4];
uint8_t lo = data[5];
uint8_t reg;
if (hi > 0x0FU || lo > 0x0FU)
return 0U;
reg = (uint8_t)((hi << 4) | lo);
if (reg < AD85020B_RAM_CH2_EQ16_A1 || reg > AD85020B_RAM_CH2_EQ18_A0)
return 0U;
*reg_out = reg;
return 1U;
}
static uint8_t amp_parse_u24(const uint8_t *nib, uint32_t *val_out)
{
uint8_t i;
uint32_t v = 0;
for (i = 0U; i < 6U; i++)
{
if (nib[i] > 0x0FU)
return 0U;
v = (v << 4) | (uint32_t)nib[i];
}
*val_out = v;
return 1U;
}
static void amp_put_reg_nibbles(uint8_t *dst, uint8_t reg)
{
dst[0] = (uint8_t)((reg >> 4) & 0x0FU);
dst[1] = (uint8_t)(reg & 0x0FU);
}
static void amp_put_u24_nibbles(uint8_t *dst, uint32_t value)
{
dst[0] = (uint8_t)((value >> 20) & 0x0FU);
dst[1] = (uint8_t)((value >> 16) & 0x0FU);
dst[2] = (uint8_t)((value >> 12) & 0x0FU);
dst[3] = (uint8_t)((value >> 8) & 0x0FU);
dst[4] = (uint8_t)((value >> 4) & 0x0FU);
dst[5] = (uint8_t)(value & 0x0FU);
}
static void amp_send_fail(uint8_t sub, uint8_t reg)
{
uint8_t resp[8] = {0xF0, 0x60, 0x07, 0x00, 0x00, 0x00, 0x7F, 0xF7};
resp[3] = sub;
amp_put_reg_nibbles(&resp[4], reg);
USART4_SendData(resp, sizeof(resp));
}
static void amp_send_ok(uint8_t sub, uint8_t reg, uint32_t value)
{
uint8_t resp[13] = {0xF0, 0x60, 0x07, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0xF7};
resp[3] = sub;
amp_put_reg_nibbles(&resp[4], reg);
amp_put_u24_nibbles(&resp[6], value);
USART4_SendData(resp, sizeof(resp));
}
static void handleAmpRamRead(uint8_t *data)
{
uint8_t reg = 0;
uint32_t value = 0;
if (amp_parse_reg(data, &reg) == 0U)
{
amp_send_fail(0x00, (uint8_t)(((data[4] & 0x0FU) << 4) | (data[5] & 0x0FU)));
return;
}
if (AD85020B_RamRead24(reg, AMP_RAM_BANK_CH2, &value) != AD85020B_OK)
{
amp_send_fail(0x00, reg);
return;
}
LOG_I("AMP", "rd 0x%02X = 0x%06lX", (unsigned)reg, (unsigned long)value);
amp_send_ok(0x00, reg, value);
}
static void handleAmpRamWrite(uint8_t *data)
{
uint8_t reg = 0;
uint32_t value = 0;
if (amp_parse_reg(data, &reg) == 0U || amp_parse_u24(&data[6], &value) == 0U)
{
amp_send_fail(0x01, (uint8_t)(((data[4] & 0x0FU) << 4) | (data[5] & 0x0FU)));
return;
}
if (AD85020B_RamWrite24(reg, AMP_RAM_BANK_CH2, value) != AD85020B_OK)
{
amp_send_fail(0x01, reg);
return;
}
value = 0;
if (AD85020B_RamRead24(reg, AMP_RAM_BANK_CH2, &value) != AD85020B_OK)
{
amp_send_fail(0x01, reg);
return;
}
LOG_I("AMP", "wr 0x%02X = 0x%06lX", (unsigned)reg, (unsigned long)value);
amp_send_ok(0x01, reg, value);
}
/*-------- 5. reset / power off 0x05 --------*/
/* F0 60 05 00 F7 : ack then soft power off (executed in main-loop context) */
static void handleDeviceReset(uint8_t *data)

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""app 套件:App SysEx 全协议(组 01~06)。
"""app 套件:App SysEx 全协议(组 01~07)。
- transport=com: 进程内复用 test_protocol_app_sim.run_tests(UART4 桥)
- transport=ble: 子进程跑 test_protocol_ble.py( bleak + BLE-MIDI framing),

117
tools/rtt_i2c_scan.py Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Scan K1 I2C1 via RTT and report amp (0x30) / mic (0x5A)."""
from __future__ import annotations
import os
import re
import sys
import time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from rtt_pitch_reg_test import ( # noqa: E402
RttSession,
connect_jlink,
find_rtt_control_block,
import_deps,
wait_rtt_ready,
)
ACK_RE = re.compile(r"^I2C ACK 0x([0-9A-Fa-f]{2})(?:\s+(\S+))?")
END_RE = re.compile(r"^I2C_SCAN_END n=(\d+)")
AMP_ADDR = 0x30
MIC_ADDR = 0x5A
def main() -> int:
if hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
import_deps()
jlink = connect_jlink("AT32F403AC")
try:
cb = find_rtt_control_block(jlink)
if cb is None:
raise SystemExit("RTT CB not found")
print(f"RTT CB @ 0x{cb:08X}", flush=True)
jlink.rtt_start(cb)
wait_rtt_ready(jlink)
sess = RttSession(jlink)
sess.pump(0.3)
found: dict[int, str] = {}
end_n: int | None = None
def consume(lines: list[str]) -> None:
nonlocal end_n
for line in lines:
text = line.strip()
if not text:
continue
if text.startswith("I2C "):
print(text, flush=True)
m = ACK_RE.match(text)
if m:
found[int(m.group(1), 16)] = m.group(2) or ""
continue
m = END_RE.match(text)
if m:
end_n = int(m.group(1))
for attempt in range(1, 5):
mark = len(sess.lines)
sess.cmd("i2c scan", settle=0.4)
deadline = time.time() + 4.0
while time.time() < deadline:
consume(sess.lines[mark:])
mark = len(sess.lines)
if end_n is not None:
break
sess.pump(0.15)
consume(sess.lines[mark:])
if end_n is not None:
break
print(f"!! no I2C_SCAN_END (try {attempt}/4)", flush=True)
if end_n is None:
print("FAIL: firmware did not answer I2C_SCAN_END", flush=True)
return 1
others = [a for a in sorted(found) if a not in (AMP_ADDR, MIC_ADDR)]
amp_ok = AMP_ADDR in found
mic_ok = MIC_ADDR in found
print("", flush=True)
print("======== I2C SCAN ========", flush=True)
print(
f"功放 AD85050 7bit 0x{AMP_ADDR:02X} / 写 0x{AMP_ADDR << 1:02X}: "
f"{'FOUND' if amp_ok else 'MISSING'}",
flush=True,
)
print(
f"麦克风 AW816 7bit 0x{MIC_ADDR:02X} / 写 0x{MIC_ADDR << 1:02X}: "
f"{'FOUND' if mic_ok else 'MISSING'}",
flush=True,
)
if others:
extra = " ".join(f"0x{a:02X}" for a in others)
print(f"其它 ACK: {extra}", flush=True)
else:
print("其它 ACK: (无)", flush=True)
print(f"I2C_SCAN_END n={end_n} unique={len(found)}", flush=True)
return 0 if amp_ok and mic_ok else 2
finally:
try:
jlink.rtt_stop()
except Exception:
pass
jlink.close()
if __name__ == "__main__":
raise SystemExit(main())

121
tools/test_amp_reg_ble.py Normal file
View File

@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""BLE SysEx test: AD85050 CH2 EQ RAM 0xA0..0xAE read/write."""
from __future__ import annotations
import argparse
import sys
import time
from test_protocol_app_sim import Results, build, expect, hexs
from test_protocol_ble import BleMidiSim, DEFAULT_NAME
def u24_nibs(v: int) -> list[int]:
return [(v >> s) & 0x0F for s in (20, 16, 12, 8, 4, 0)]
def nibs_u24(n) -> int:
v = 0
for x in n:
v = (v << 4) | (x & 0x0F)
return v
def reg_nibs(reg: int) -> tuple[int, int]:
return (reg >> 4) & 0x0F, reg & 0x0F
def main() -> int:
if hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
ap = argparse.ArgumentParser(description="K1 功放寄存器 BLE 读写测试")
ap.add_argument("--ble-name", default=DEFAULT_NAME)
ap.add_argument("--ble-address")
ap.add_argument("--no-unpair", action="store_true")
args = ap.parse_args()
print(f"连接 BLE {args.ble_name!r} {args.ble_address or ''} ...", flush=True)
sim = BleMidiSim(
name=args.ble_name,
address=args.ble_address,
transport="midi",
unpair=not args.no_unpair,
)
R = Results()
try:
print(
f"已连接 {sim.info.get('name')} {sim.info.get('address')}",
flush=True,
)
r = sim.query(build(0x01, 0x01), timeout=2.0)
ok, d = expect(r, (0x60, 0x01, 0x01), 6)
R.add("01 01 连接探测", "PASS" if ok else "FAIL", d)
targets = (
(0xA0, "CH2EQ16A1"),
(0xA4, "CH2EQ16A0"),
(0xA5, "CH2EQ17A1"),
(0xA9, "CH2EQ17A0"),
(0xAA, "CH2EQ18A1"),
(0xAE, "CH2EQ18A0"),
)
originals: dict[int, int] = {}
for reg, name in targets:
rh, rl = reg_nibs(reg)
r = sim.query(build(0x07, 0x00, rh, rl), timeout=1.5)
ok, d = expect(r, (0x60, 0x07, 0x00, rh, rl), 13)
if ok:
val = nibs_u24(r[6:12])
originals[reg] = val
d += f" {name}=0x{val:06X}"
R.add(f"07 00 读 0x{reg:02X} {name}", "PASS" if ok else "FAIL", d)
probe = 0x123456
if 0xA4 in originals:
rh, rl = reg_nibs(0xA4)
r = sim.query(build(0x07, 0x01, rh, rl, *u24_nibs(probe)), timeout=1.5)
ok, d = expect(r, (0x60, 0x07, 0x01, rh, rl), 13)
if ok:
got = nibs_u24(r[6:12])
ok = got == probe
d += f" wrote=0x{probe:06X} echo=0x{got:06X}"
R.add("07 01 写 0xA4=0x123456", "PASS" if ok else "FAIL", d)
r = sim.query(build(0x07, 0x00, rh, rl), timeout=1.5)
ok = r is not None and len(r) == 13 and nibs_u24(r[6:12]) == probe
R.add("07 00 回读 0xA4=0x123456", "PASS" if ok else "FAIL", hexs(r))
r = sim.query(
build(0x07, 0x01, rh, rl, *u24_nibs(originals[0xA4])), timeout=1.5
)
ok = r is not None and len(r) == 13 and nibs_u24(r[6:12]) == originals[0xA4]
R.add(
f"07 01 恢复 0xA4=0x{originals[0xA4]:06X}",
"PASS" if ok else "FAIL",
hexs(r),
)
else:
R.add("07 01 写 0xA4", "SKIP", "读 0xA4 失败")
R.add("07 00 回读 0xA4", "SKIP", "读 0xA4 失败")
R.add("07 01 恢复 0xA4", "SKIP", "读 0xA4 失败")
rh, rl = reg_nibs(0xB0)
r = sim.query(build(0x07, 0x00, rh, rl), timeout=1.5)
ok = r is not None and len(r) == 8 and r[3] == 0x00 and r[6] == 0x7F
R.add("07 00 非法地址 0xB0", "PASS" if ok else "FAIL", hexs(r))
time.sleep(0.05)
return R.summary()
finally:
sim.close()
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -381,6 +381,44 @@ def run_tests(sim, allow_poweroff=False):
R.add(f"06 {sub:02X} {name}", "SENT", "无应答属正常")
time.sleep(0.05)
# ---------------- 7. 功放 RAM 0x07 (CH2 EQ16/17/18, 半字节编码) ----------------
def _u24_nibs(v):
return [(v >> s) & 0x0F for s in (20, 16, 12, 8, 4, 0)]
def _nibs_u24(n):
v = 0
for x in n:
v = (v << 4) | (x & 0x0F)
return v
r = sim.query(build(0x07, 0x00, 0x0A, 0x04), timeout=1.0)
ok, d = expect(r, (0x60, 0x07, 0x00, 0x0A, 0x04), 13)
orig_a4 = _nibs_u24(r[6:12]) if ok else None
if ok:
d += f" 0xA4=0x{orig_a4:06X}"
R.add("07 00 读 CH2EQ16A0(0xA4)", "PASS" if ok else "FAIL", d)
if orig_a4 is not None:
probe = 0x123456
r = sim.query(build(0x07, 0x01, 0x0A, 0x04, *_u24_nibs(probe)), timeout=1.0)
ok, d = expect(r, (0x60, 0x07, 0x01, 0x0A, 0x04), 13)
if ok:
got = _nibs_u24(r[6:12])
ok = got == probe
d += f" wrote=0x{probe:06X} read=0x{got:06X}"
R.add("07 01 写 CH2EQ16A0=0x123456", "PASS" if ok else "FAIL", d)
r = sim.query(build(0x07, 0x00, 0x0A, 0x04), timeout=1.0)
ok = r is not None and len(r) == 13 and _nibs_u24(r[6:12]) == probe
R.add("07 00 回读=0x123456", "PASS" if ok else "FAIL", hexs(r))
sim.query(build(0x07, 0x01, 0x0A, 0x04, *_u24_nibs(orig_a4)), timeout=1.0)
else:
R.add("07 01 写 CH2EQ16A0", "SKIP", "读 0xA4 失败")
R.add("07 00 回读", "SKIP", "读 0xA4 失败")
r = sim.query(build(0x07, 0x00, 0x0B, 0x00), timeout=1.0)
ok = r is not None and len(r) == 8 and r[3] == 0x00 and r[6] == 0x7F
R.add("07 00 非法地址 0xB0", "PASS" if ok else "FAIL", hexs(r))
# ---------------- 5. 复位/关机 0x05 ----------------
if allow_poweroff:
time.sleep(0.3)