From 4a3b111ce4dcfef5254c5110bf1180ea456d0d0b Mon Sep 17 00:00:00 2001 From: gcw_4spBpAfv Date: Tue, 28 Apr 2026 16:58:51 +0800 Subject: [PATCH] refine power module --- app.yaml | 1 - config.py | 71 +++++++-------------------------------------- design_doc/debug.md | 3 +- main.py | 16 +++++++++- network.py | 5 +++- power.py | 64 ++++++++++++++++++++++++++++++++++++---- vision.py | 23 ++++----------- 7 files changed, 93 insertions(+), 90 deletions(-) diff --git a/app.yaml b/app.yaml index d95064f..09d1b9a 100644 --- a/app.yaml +++ b/app.yaml @@ -8,7 +8,6 @@ files: - 4g_download_manager.py - app.yaml - archery_netcore.cpython-311-riscv64-linux-gnu.so - - aruco_detector.py - at_client.py - camera_manager.py - cameraParameters.xml diff --git a/config.py b/config.py index e1a5307..d6b8043 100644 --- a/config.py +++ b/config.py @@ -65,21 +65,20 @@ LOG_FILE = APP_DIR + "/app.log" BACKUP_BASE = APP_DIR + "/backups" # ==================== 硬件配置 ==================== -# WiFi模块开关(True=有WiFi模块,False=无WiFi模块) -HAS_WIFI_MODULE = True # 根据实际硬件情况设置 - # UART配置 UART4G_DEVICE = "/dev/ttyS2" UART4G_BAUDRATE = 115200 DISTANCE_SERIAL_DEVICE = "/dev/ttyS1" DISTANCE_SERIAL_BAUDRATE = 9600 -# I2C配置(根据WiFi模块开关自动选择) -# 无WiFi模块:I2C_BUS_NUM = 1,引脚:P18(I2C1_SCL), P21(I2C1_SDA) -# 有WiFi模块:I2C_BUS_NUM = 5,引脚:A15(I2C5_SCL), A27(I2C5_SDA) -I2C_BUS_NUM = 5 if HAS_WIFI_MODULE else 1 +# I2C:板载 WiFi 方案固定 I2C5,引脚 A15(SCL) / A27(SDA),供 INA226 等 +I2C_BUS_NUM = 5 INA226_ADDR = 0x40 +# False=完全不访问 INA226(无电源计量板或未供电时避免 ~2.5s writeto 重试与底层 write failed 日志);量产有芯片时设为 True +INA226_ENABLE = True +# True=整总线 I2C scan 探测 INA226(在部分平台上极慢,可达 ~90s+);False=仅对 INA226_ADDR 快速探测(writeto 空写) +INA226_PROBE_FULL_BUS_SCAN = False REG_CONFIGURATION = 0x00 REG_BUS_VOLTAGE = 0x02 REG_CURRENT = 0x04 # 电流寄存器 @@ -165,7 +164,7 @@ TRIANGLE_EARLY_EXIT_CANDIDATES = 4 # 找到多少个候选就提前停 TRIANGLE_ADAPTIVE_BLOCK_SIZES = (11, 21) # 自适应阈值 blockSize 尝试列表;置空 () 可完全关闭 adaptiveThreshold TRIANGLE_MAX_FILTERED_FOR_COMBO = 10 # 参与四点组合评分的最大候选数(超过则截断到最可能的一部分) -FLASH_LASER_WHILE_SHOOTING = True # 是否在拍摄时闪一下激光(True=闪,False=不闪) +FLASH_LASER_WHILE_SHOOTING = False # 是否在拍摄时闪一下激光(True=闪,False=不闪) FLASH_LASER_DURATION_MS = 1000 # 闪一下激光的持续时间(毫秒) # ==================== 显示配置 ==================== @@ -185,67 +184,17 @@ MAX_BACKUPS = 5 LOG_MAX_BYTES = 10 * 1024 * 1024 # 10MB LOG_BACKUP_COUNT = 5 -# ==================== 引脚映射配置 ==================== -# 无WiFi模块的引脚映射(I2C1) -PIN_MAPPINGS_NO_WIFI = { - "A18": "UART1_RX", - "A19": "UART1_TX", - "A29": "UART2_RX", - "A28": "UART2_TX", - "P18": "I2C1_SCL", - "P21": "I2C1_SDA", -} - -# 有WiFi模块的引脚映射(I2C5) -PIN_MAPPINGS_WITH_WIFI = { +# ==================== 引脚映射配置(板载 WiFi,I2C5)==================== +PIN_MAPPINGS = { "A18": "UART1_RX", "A19": "UART1_TX", "A29": "UART2_RX", "A28": "UART2_TX", "A15": "I2C5_SCL", "A27": "I2C5_SDA", - "A24": "GPIOA24", # 电源板的引脚 + "A24": "GPIOA24", # 电源板关机控制 } -# 根据WiFi模块开关选择引脚映射 -PIN_MAPPINGS = PIN_MAPPINGS_WITH_WIFI if HAS_WIFI_MODULE else PIN_MAPPINGS_NO_WIFI - -# ==================== ArUco标定配置 ==================== -USE_ARUCO = False # 是否使用ArUco标定(True=使用ArUco,False=使用传统黄色靶心检测) - -# ArUco标记配置 -if USE_ARUCO: - import cv2 - ARUCO_DICT_TYPE = cv2.aruco.DICT_4X4_50 # ArUco字典类型 - ARUCO_MARKER_SIZE_MM = 40 # ArUco标记边长(毫米) - ARUCO_MARKER_IDS = [0, 1, 2, 3] # 四个角的ArUco标记ID - - # 靶纸物理尺寸(毫米) - TARGET_PAPER_SIZE_MM = 400 # 靶纸边长 400mm x 400mm - - # ArUco标记在靶纸上的中心坐标(毫米,以靶纸中心为原点) - # 靶纸坐标系:中心(0,0),X向右,Y向下(图像坐标系) - # 四个角位置:(20,20), (20,380), (380,380), (380,20) - # 转换为以中心为原点的坐标: - # 左上角(0): (-180, -180) -> 实际(20,20)相对于中心(200,200) = (-180,-180) - # 右上角(1): (180, -180) -> 实际(380,20)相对于中心 = (180,-180) - # 右下角(2): (180, 180) -> 实际(380,380)相对于中心 = (180,180) - # 左下角(3): (-180, 180) -> 实际(20,380)相对于中心 = (-180,180) - ARUCO_MARKER_POSITIONS_MM = { - 0: (-180, -180), # 左上角 - 1: (180, -180), # 右上角 - 2: (180, 180), # 右下角 - 3: (-180, 180), # 左下角 - } - - # 靶心(黄心)在靶纸上的位置(毫米,相对于靶纸中心) - # 标准靶纸靶心就在正中心 - TARGET_CENTER_OFFSET_MM = (0, 0) - - # ArUco检测参数 - ARUCO_MIN_MARKER_PERIMETER_RATE = 0.03 # 最小标记周长比例(相对于图像) - ARUCO_CORNER_REFINEMENT_METHOD = cv2.aruco.CORNER_REFINE_SUBPIX # 角点精修方法 - # ==================== 电源配置 ==================== AUTO_POWER_OFF_IN_SECONDS = 10 * 60 # 自动关机时间(秒),0表示不自动关机 diff --git a/design_doc/debug.md b/design_doc/debug.md index 88a48a1..81c840b 100644 --- a/design_doc/debug.md +++ b/design_doc/debug.md @@ -26,8 +26,7 @@ program exit failed. exit code: 1. 解决方案: - 从日志看,就是开始发送登录信息之后就崩溃了。出发了底层的read failed。经过排查,是一定要插上电源板的数据连线,以及电源板要插上电池。这个应该是 - 登录时需要读电源电压数据, + 从日志看,就是开始发送登录信息之后就崩溃了。出发了底层的read failed。经过排查,是一定要插上电源板的数据连线,以及电源板要插上电池。这个应该是登录时需要读电源电压数据。后面我们已经优化了日志,而且增加了对ina226的试探,但发现ina226不存在的时候,就直接返回电压和电流为0.0。而且,一定要注意,在新配套的电源板和核心板上面,才能正常读到电流和电压。 3. a)问题描述:202609 批次的拓展版,有一块maixcam的蓝灯常亮,询问maixcam的人,他们觉得应该是卡没有插好。但是拓展版上的激光口挡住了数据卡的出口, 没法拔出检查, diff --git a/main.py b/main.py index dac68cf..9dc0fb3 100644 --- a/main.py +++ b/main.py @@ -14,6 +14,7 @@ from maix.peripheral import adc import _thread import os import json +import time as wall_time # 导入新模块 import config @@ -96,12 +97,25 @@ def cmd_str(): # 3. 初始化激光模块(串口 + 开机关闭激光防误触发) laser_manager.init() - # 3. 初始化 INA226 电量监测芯片 + # 3. 初始化 INA226 电量监测芯片(与后续相机启动之间的耗时,便于定位启动卡顿) + _w_boot = wall_time.time() + print(f"[BOOT] init_ina226 开始 wall_s={_w_boot:.3f}") init_ina226() + print(f"[BOOT] init_ina226 结束 wall +{int(round((wall_time.time() - _w_boot) * 1000))} ms") # 4. 初始化显示和相机 + _w_boot = wall_time.time() + print( + f"[BOOT] init_camera({getattr(config, 'CAMERA_WIDTH', 640)}x{getattr(config, 'CAMERA_HEIGHT', 480)}) " + f"开始 wall_s={_w_boot:.3f}" + ) camera_manager.init_camera(getattr(config, "CAMERA_WIDTH", 640), getattr(config, "CAMERA_HEIGHT", 480)) + print(f"[BOOT] init_camera 结束 wall +{int(round((wall_time.time() - _w_boot) * 1000))} ms") + + _w_boot = wall_time.time() + print(f"[BOOT] init_display 开始 wall_s={_w_boot:.3f}") camera_manager.init_display() + print(f"[BOOT] init_display 结束 wall +{int(round((wall_time.time() - _w_boot) * 1000))} ms") # ==================== 第二阶段:软件初始化 ==================== diff --git a/network.py b/network.py index 6c457b6..6c774f7 100644 --- a/network.py +++ b/network.py @@ -1984,7 +1984,10 @@ class NetworkManager: elif inner_cmd == 4: # 上报电量 voltage = get_bus_voltage() battery_percent = voltage_to_percent(voltage) - battery_data = {"battery": battery_percent, "voltage": round(voltage, 3)} + battery_data = { + "battery": battery_percent, + "voltage": round(float(voltage), 3), + } self.safe_enqueue(battery_data, 2) self.logger.info(f"电量上报: {battery_percent}%") elif inner_cmd == 5: # OTA 升级 diff --git a/power.py b/power.py index c7aebfa..2dcecc4 100644 --- a/power.py +++ b/power.py @@ -6,13 +6,36 @@ """ import config from logger_manager import logger_manager +from maix import time as maix_time + + +_INA226_PRESENT = None + + +def _ina226_ready() -> bool: + """ + 是否允许访问 INA226。 + + 重要: + - 这里刻意不做任何 I2C 探测/读写。 + - 经验上,在 INA226 未供电/未响应时,I2C 的 readfrom_mem 可能直接触发底层崩溃(SIGSEGV),try/except 无法拦截。 + - 因此只在开机 init_ina226() 成功后才允许后续读电压/电流。 + """ + return bool(getattr(config, "INA226_ENABLE", True)) and (_INA226_PRESENT is True) def write_register(reg, value): """写入INA226寄存器""" from hardware import hardware_manager + logger = logger_manager.logger data = [(value >> 8) & 0xFF, value & 0xFF] - hardware_manager.bus.writeto_mem(config.INA226_ADDR, reg, bytes(data)) + # 某些底层驱动在失败时只打印 “write failed” 并返回 -1,而不是抛异常; + # 为避免误判“初始化成功”导致后续 readfrom_mem SIGSEGV,这里把失败显式转成异常。 + ret = hardware_manager.bus.writeto_mem(config.INA226_ADDR, reg, bytes(data)) + if isinstance(ret, int) and ret < 0: + if logger: + logger.error(f"[INA226] writeto_mem 失败: addr=0x{config.INA226_ADDR:02X} reg=0x{reg:02X} ret={ret}") + raise OSError(ret) def read_register(reg): @@ -24,14 +47,39 @@ def read_register(reg): def init_ina226(): """初始化 INA226 芯片:配置模式 + 校准值""" - write_register(config.REG_CONFIGURATION, 0x4527) - write_register(config.REG_CALIBRATION, config.CALIBRATION_VALUE) + global _INA226_PRESENT + logger = logger_manager.logger + if not getattr(config, "INA226_ENABLE", True): + if logger: + logger.info("[INA226] INA226_ENABLE=False,跳过初始化与 I2C 探测") + # 显式标记不可用,避免后续误读 + _INA226_PRESENT = False + return False + try: + # 仅通过“写寄存器成功”来判定可用,避免额外的读操作触发底层崩溃 + write_register(config.REG_CONFIGURATION, 0x4527) + write_register(config.REG_CALIBRATION, config.CALIBRATION_VALUE) + _INA226_PRESENT = True + return True + except Exception as e: + _INA226_PRESENT = False + if logger: + logger.error(f"[INA226] 初始化失败:{e}") + return False def get_bus_voltage(): - """读取总线电压(单位:V)""" - raw = read_register(config.REG_BUS_VOLTAGE) - return raw * 1.25 / 1000 + """读取总线电压(单位:V)。未探测到 INA226 或读失败时返回 0.0(上报用,避免 null)。""" + logger = logger_manager.logger + if not _ina226_ready(): + return 0.0 + try: + raw = read_register(config.REG_BUS_VOLTAGE) + return raw * 1.25 / 1000 + except Exception as e: + if logger: + logger.error(f"[INA226] 读取电压失败:{e}") + return 0.0 def get_current(): @@ -44,6 +92,8 @@ def get_current(): Current_LSB = 0.001 × CALIBRATION_VALUE / 4096 """ try: + if not _ina226_ready(): + return 0.0 raw = read_register(config.REG_CURRENT) # INA226 电流寄存器是16位有符号整数 # 最高位是符号位:0=正(充电),1=负(放电) @@ -92,6 +142,8 @@ def is_charging(threshold_ma=10.0): def voltage_to_percent(voltage): """根据电压估算电池百分比(查表插值)""" + if voltage is None: + return 0 points = [ (4.20, 100), (4.10, 95), (4.05, 85), (4.00, 75), (3.95, 65), (3.90, 55), (3.85, 45), (3.80, 35), (3.75, 25), (3.70, 15), diff --git a/vision.py b/vision.py index fdaf2fb..0293c79 100644 --- a/vision.py +++ b/vision.py @@ -14,10 +14,6 @@ from maix import image import config from logger_manager import logger_manager -# 导入ArUco检测器(如果启用) -if config.USE_ARUCO: - from aruco_detector import detect_target_with_aruco, aruco_detector - # 存图队列 + worker _save_queue = queue.Queue(maxsize=16) _save_worker_started = False @@ -927,18 +923,9 @@ def detect_target(frame, laser_point=None): 与detect_circle_v3保持相同的返回格式 """ logger = logger_manager.logger - - if config.USE_ARUCO: - # 使用ArUco检测 - if logger: - logger.debug("[VISION] 使用ArUco标记检测靶心") - - # 延迟导入以避免循环依赖 - from aruco_detector import detect_target_with_aruco - return detect_target_with_aruco(frame, laser_point) - else: - # 使用传统黄色靶心检测 - if logger: - logger.debug("[VISION] 使用传统黄色靶心检测") - return detect_circle_v3(frame, laser_point) + + # 项目当前统一使用黄色靶心检测(圆/椭圆),不再保留 ArUco 路径 + if logger: + logger.debug("[VISION] 使用传统黄色靶心检测") + return detect_circle_v3(frame, laser_point)