ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

UDS+CAN本地OTA实战:嵌入式固件安全升级全链路解析

UDS+CAN本地OTA实战:嵌入式固件安全升级全链路解析 1. 这不是“刷个固件”那么简单UDSCAN本地OTA的本质是嵌入式系统的临床手术你手头有一台带CAN总线的ECU比如汽车BCM、电机控制器或工业PLC它已经部署在设备里不能拆机、不能断电、不能停机——但新版本固件必须上安全补丁得打功能逻辑要迭代。这时候“OTA升级”四个字听起来很酷可真落到CAN总线上它就不是手机App更新那种点几下就行的事。它是一场在毫秒级实时通信约束下、无外部网络介入、仅靠本地诊断通道完成的嵌入式系统“临床手术”。核心关键词UDS统一诊断服务、CAN控制器局域网、OTA空中下载三者叠加意味着你必须同时驾驭协议栈、硬件时序、内存管理、校验容错四大维度。这不是写个HTTP下载脚本就能搞定的而是要在250kbps甚至125kbps的CAN波特率下把几MB的固件二进制文件切成几十到上百帧报文逐帧发送、逐帧应答、逐帧校验中间任何一帧丢失、错位、超时整个流程就得回滚重来。我做过7个不同车型的BCM OTA项目最深的体会是UDS不是工具是语言CAN不是线缆是血管OTA不是动作是状态迁移过程。它适合两类人一类是正在啃AUTOSAR底层、调试CANoe仿真的工程师另一类是手握STM32F4/F7或NXP S32K芯片、正被客户催着交付“支持远程升级”功能的嵌入式开发负责人。如果你还在用串口XMODEM硬刷或者以为“找个UDS库memcpy一下”就能上线这篇就是为你写的实战复盘——不讲ISO 14229-1标准原文只说你烧录第3次失败时示波器上看到的那帧0x7DF应答里NRC 0x78请求正确但响应未准备好背后的真实硬件阻塞点在哪。2. 整体架构设计为什么必须放弃“TCP/IP思维”回归CAN的物理层真相2.1 本地OTA ≠ 网络OTACAN总线的三大物理枷锁很多人一听到OTA本能想到Wi-Fi/4G模块HTTP/HTTPS下载差分包解压。但在CAN本地场景下这套逻辑从根上就错了。CAN总线没有IP地址、没有TCP三次握手、没有ACK重传机制——它只有ID仲裁、CSMA/CD载波监听多路访问/冲突检测、以及极其有限的错误帧反馈。这意味着你的OTA架构必须彻底重构带宽枷锁标准CAN 2.0A/B最大理论带宽250kbps实际有效载荷约180kbps扣除帧头、CRC、间隔。升级一个512KB固件理论最小传输时间 512×1024×8 ÷ 180000 ≈ 23.2秒。这还没算UDS协议开销每帧需加SID、sub-function、data identifier等实测往往要35秒以上。你若按以太网思维设计“后台静默下载”ECU可能在第20秒就因看门狗复位而中断。帧长枷锁CAN 2.0单帧最多8字节数据CAN FD可扩至64字节但多数车载ECU仍用传统CAN。升级固件时你无法像TCP那样发一个大包必须把固件二进制流切片成“CAN帧序列”。每帧都要带UDS服务标识如0x36下载请求、块序号、校验和。更麻烦的是UDS规定单次请求最大数据长度由服务器ECU通过0x83服务通信控制告知客户端刷写工具常见值为4~16字节——这意味着512KB固件要拆成32,000~128,000帧每一帧都需独立应答。资源枷锁ECU RAM通常仅64~256KBFlash擦除粒度为2KB~32KB扇区。OTA过程中你既要缓存当前接收帧又要预留RAM运行刷写算法如AES解密、CRC32校验还要保证应用区不被覆盖。我见过某国产BMS项目因未预留足够RAM做双缓冲升级到第87%时RAM溢出ECU直接锁死只能拆机用JTAG恢复。提示别迷信“CAN FD能解决一切”。FD虽提升带宽但ECU端需同步升级收发器与MCU外设且UDS协议栈需重写以支持动态数据长度。对存量项目务实做法是优化传统CAN下的帧调度策略而非强行上FD。2.2 UDS协议栈不是黑盒必须亲手拆解的5个关键服务UDSISO 14229-1不是拿来即用的SDK它是诊断会话的“宪法”。本地OTA依赖其中5个核心服务每个服务的触发条件、参数约束、错误码含义都直接影响升级成败0x10Diagnostic Session Control切换诊断会话模式。OTA必须在Extended Diagnostic Session0x03下进行因为Default Session0x01禁止写Flash操作。实操中常见错误是未先发0x10 0x03就直奔0x31Routine ControlECU返回NRC 0x7F服务不支持。0x22Read Data by Identifier读取ECU状态。必须在刷写前读取关键ID0xF190软件版本、0xF180编程电压、0xF195Flash擦除状态。某次项目中客户ECU在-40℃环境下0xF180返回电压不足我们却跳过此步直接擦除导致扇区损坏。0x31Routine Control执行预刷写例程。典型调用0x31 0x01 0xFF00擦除指定Flash扇区。注意0xFF00是子功能参数需按ECU手册填入起始地址与长度。曾有团队把长度单位错当字节而非扇区数结果擦除了Bootloader区。0x34/0x36/0x37Request Download / Transfer Data / Request Transfer ExitUDS刷写的“铁三角”。0x34请求下载ECU返回最大块长度与内存地址0x36分块传输数据0x37确认传输结束。关键陷阱0x36每帧数据长度必须≤0x34返回值且块序号Block Sequence Counter必须严格递增丢一帧就全盘失败。0x2EWrite Data by Identifier写入校验密钥或激活标志。OTA完成后需写0xF196编程完成标志并重启。若遗漏此步ECU下次启动仍运行旧固件。注意NRCNegative Response Code不是报错是诊断对话的“语法反馈”。例如NRC 0x33安全访问拒绝意味着你没通过Seed-Key认证NRC 0x72一般编程失败说明Flash写入异常需查电源纹波NRC 0x22条件不满足往往是0x22读取的状态未达标。把NRC当错误日志看比查代码更高效。2.3 本地OTA的三种落地形态选错架构半年白干根据硬件资源与安全要求本地OTA有三种主流实现路径没有优劣只有适配纯Host-ECU模式推荐新手PC或工装板作为Host通过USB-CAN适配器如Peak PCAN-USB连接ECU。Host端运行Python脚本基于python-can库ECU端裸机实现UDS协议栈。优势是调试直观可用CANoe/CANalyzer抓包分析劣势是依赖外部设备无法自主触发。我第一版STM32 OTA就是此模式用VSCode pyocd在线调试UDS状态机。T-Box桥接模式车规主流T-Box作为中间网关接收云端指令后通过内部CAN与ECU通信。此时T-Box需实现UDS ClientECU仍是Server。难点在于T-Box与ECU间的会话同步——T-Box发0x10 0x03后ECU响应延迟若超100msT-Box可能误判超时。某项目因此在高温环境下出现间歇性升级失败最终在T-Box侧加了50ms软延时才解决。ECU自举模式高阶挑战ECU自身集成轻量级UDS Client能主动从SD卡/U盘读取ota.bin通过内部CAN或LIN与Bootloader通信。这要求ECU具备文件系统FatFS与多任务调度FreeRTOS。我们为某农机控制器做的方案用SPI Flash模拟U盘Bootloader从0x08000000读取ota.bin再跳转到Application区执行刷写——全程无需外部设备但开发周期延长3个月。3. 核心细节解析从固件打包到Flash擦写每一步都是雷区3.1 固件镜像的外科手术式预处理OTA固件不是原始编译输出的.bin文件它必须经过四层“外科手术”才能上CAN第一层地址对齐与填充。ARM Cortex-M芯片Flash擦除以扇区为单位如STM32F407是2KB但固件起始地址常为0x08000000末尾可能不足一扇区。必须用0xFF填充至扇区边界。我用Python脚本自动计算padding_len (sector_size - (len(bin_data) % sector_size)) % sector_size再追加b\xFF * padding_len。曾因填充字节错用0x00导致擦除后该扇区全0ECU启动失败。第二层添加UDS元数据头。在固件前插入16字节Header4字节Magic0x55AA55AA、2字节CRC16Header固件、2字节固件长度Little Endian、4字节版本号、4字节时间戳。ECU Bootloader在0x34服务中解析此Header验证CRC16后再允许下载。某次客户提供的固件头CRC错我们花两天查硬件SPI读取时序最后发现是Python struct.pack(H, crc)用了大端而MCU期望小端。第三层分块加密与签名。为防固件被篡改需用AES-128-CBC加密密钥存于ECU OTP区再用ECDSA签名。关键点IV向量必须随块变化否则相同明文块加密后密文相同易被重放攻击。我们采用“块序号时间戳SHA256截取”生成IV确保每块唯一。第四层生成CAN帧序列文件。将加密后固件按UDS最大块长如12字节切片每片封装为CAN帧[0x36][BlockSeq][Data...]。用Excel生成帧列表导出CSV供Host脚本读取。注意BlockSeq从0x01开始0x00 reserved且必须连续。某次因Excel自动排序打乱序号升级到一半ECU返回NRC 0x31请求超出范围。实操心得别用现成OTA提取器APP。那些工具为通用性牺牲精度比如对CAN FD支持不全、忽略ECU特定NRC处理。我坚持手写Python生成器120行代码可控性强且能嵌入CRC校验与日志输出。3.2 ECU端UDS协议栈的轻量化实现要点在MCU资源紧张时UDS栈不能照搬AUTOSAR ComStack。我们基于FreeRTOS在STM32F767上实现的精简版仅2.3KB代码核心原则是状态机驱动非中断驱动CAN接收用HAL_CAN_RxCpltCallback触发但UDS逻辑在单独Task中轮询处理。避免在中断里做耗时操作如Flash擦除防止优先级反转。Task周期设为1ms足够响应UDS超时默认50ms。内存池预分配杜绝malloc为UDS Request/Response各分配256字节Buffer用环形队列管理。Flash擦除时Buffer用于暂存待写数据避免动态分配失败。某次项目因未限制Buffer大小升级大固件时malloc返回NULLECU死循环。NRC精准映射不泛化处理定义结构体typedef struct { uint8_t sid; uint8_t nrc; char* desc; } uds_nrc_map_t;将NRC与具体原因绑定。例如0x33对应“Security Access未解锁”0x72对应“Flash写入失败检查VDD电压”。调试时直接打印desc比查ISO标准快10倍。超时机制双保险全局超时如0x34后30s未收到0x74响应 单帧超时如0x36后50ms未收到0x76。超时后自动发0x37退出并清空所有状态。曾有ECU因CAN总线干扰丢帧无超时机制导致卡死。3.3 Flash擦写的安全边界控制这是OTA最危险环节擦错地址变砖。我们的防护策略分三层第一层地址白名单校验。在0x34服务中ECU解析请求的内存地址范围只允许擦写Application区如0x0800C000~0x0807FFFFBootloader区0x08000000~0x0800BFFF绝对禁止。校验代码if ((addr APP_START_ADDR) || (addr length APP_END_ADDR)) { send_nrc(0x31); // 请求超出范围 return; }第二层电压实时监测。在擦除前调用HAL_ADC_Start(hadc1)读取VDD低于2.7VSTM32F7最低工作电压则返回NRC 0x72。某次实验室测试正常量产车在低温启动时VDD瞬降我们加了10ms滤波才稳定。第三层双备份校验。擦除后立即读回验证逐字节比对。若发现差异记录错误扇区地址到EEPROM下次启动时告警。曾因Flash质量批次问题某扇区擦除后读回全0xFF双备份机制让我们快速定位到供应商批次。踩过的坑别信“擦除一次就行”。NOR Flash需多次擦除才能稳定尤其在高温下。我们最终方案是对每个扇区执行3次擦除-验证循环第三次失败才报错。虽然慢10%但量产零返修。4. 实操全流程从CANoe仿真到实车刷写我的7步标准化作业4.1 Step 1用CANoe搭建虚拟ECU环境零硬件成本验证在敲真实ECU代码前先用CANoe建模验证协议逻辑。关键步骤创建Network添加CAN Channel波特率设为500kbps匹配目标ECU。添加Node命名为“Virtual_ECU”加载CAPL脚本。CAPL脚本核心逻辑on key u { // 模拟UDS 0x10 0x03 响应 message 0x7DF msg; msg.byte(0) 0x02; // len msg.byte(1) 0x50; // positive response SID msg.byte(2) 0x03; // session type output(msg); }配置Diagnostic Console导入CDD文件描述UDS服务设置Security Access Seed-Key算法如XOR 0xAA。这样你能在PC上用Diagnostic Test Panel发任意UDS命令观察Virtual_ECU响应验证NRC逻辑。我用此法提前发现3处状态机漏洞节省2周硬件调试时间。4.2 Step 2ECU Bootloader开发与调试最耗时环节Bootloader是OTA的守门人必须独立于Application运行。我们采用ST官方AN2606方案但做了关键增强跳转前校验Application区首4字节为Magic0xDEADBEAFBootloader启动时校验。若失败强制进入DFU模式。双Bank设计Application区划分为Bank A主与Bank B备用。OTA时下载到Bank B验证成功后更新跳转指针。即使升级中断仍可从Bank A启动。调试接口保留Bootloader禁用SWD引脚但留出UART打印日志。用printf(BL: Erase addr 0x%08X\r\n, addr);输出关键动作接USB-TTL看串口比JTAG更直观。调试技巧用ST-Link Utility手动擦除Bank B再用Keil下载Bootloader最后用J-Flash烧录Application测试跳转。确保Bootloader能识别Application的Vector Table Offset RegisterVTOR。4.3 Step 3UDS服务实现与单元测试在Bootloader基础上添加UDS服务Task。重点测试三个场景场景10x34请求下载Host发0x34 0x00 0x44 0x00 0x00 0x08 0x00 C0 00 00请求下载到0x0800C000ECU应答0x74 0x00 0x44 0x00 0x00 0x08 0x00 C0 00 00 0x0C最大块长12字节错误点ECU未校验地址范围直接返回0x74导致后续擦除越界。场景20x36传输数据Host发0x36 0x01 [12字节数据]ECU应答0x76 0x01块序号确认错误点ECU未校验BlockSeq连续性收到0x01后直接收0x03漏掉0x02导致数据错位。场景30x37退出传输Host发0x37ECU应答0x77然后执行Flash校验。错误点ECU未等待校验完成就返回0x77Host误以为成功实则Flash写入失败。单元测试用Unity框架每个服务写3个测试用例正常、NRC、边界。覆盖率需≥90%尤其NRC分支。4.4 Step 4Host端刷写工具开发Python实战Host工具是OTA的“手术刀”我们用Pythonpython-can实现核心模块can_bus.py封装CAN初始化、发送、接收支持PCAN、SocketCAN。uds_client.py实现UDS服务调用含超时重试3次。ota_processor.py解析ota.bin生成帧序列计算CRC。main.py流程控制含进度条与日志。关键代码片段0x36传输def transfer_block(self, block_seq, data): payload [0x36, block_seq] list(data) self.send_can_frame(0x7DF, payload) # 等待0x76响应 start_time time.time() while time.time() - start_time 0.05: msg self.bus.recv(timeout0.01) if msg and msg.arbitration_id 0x7E8 and msg.data[0] 0x76: if msg.data[1] block_seq: return True return False # 超时实测对比用此工具升级512KB固件平均耗时38.2秒成功率99.8%1000次测试失败2次均为CAN总线干扰。4.5 Step 5实车CAN总线抗干扰加固实验室通了上车必出问题。我们针对实车环境做三项加固终端电阻校准标准CAN总线两端需120Ω电阻。实车中常因改装线束导致阻抗失配引起信号反射。用万用表实测整车CAN_H-CAN_L电阻若≠120Ω±5%在诊断口加装可调电阻模块。共模扼流圈在ECU CAN收发器如TJA1050输入端串入共模电感如Bourns SRF1260抑制高频噪声。某次EMC测试辐射超标加此器件后降低12dB。报文过滤ECU CAN接收过滤器只允许ID 0x7DF诊断请求与0x7E8诊断响应屏蔽其他报文。避免总线负载过高时UDS帧被延迟。经验实车刷写前务必用CANoe录制10分钟总线流量用Statistic分析Bus Load。若30%需协调客户暂停其他ECU发送非必要报文。4.6 Step 6升级失败的现场急救包OTA失败不是终点是调试起点。我们给现场工程师配“急救包”第一步读取DTC。用诊断仪发0x19 0x02 0x09读取当前DTC若返回0x59 0x02 0x09 0x00则无故障若有DTC如U0100CAN通信丢失先查物理层。第二步抓取失败帧。用PCAN-USB实时捕获过滤ID 0x7DF/0x7E8找最后一帧0x36或0x37的响应。若无0x76说明ECU未处理若有0x7F看NRC字节。第三步检查ECU状态。发0x22 0xF195Flash状态若返回0x00表示空闲0x01表示忙0xFF表示错误。第四步强制恢复。短接ECU Bootloader跳线用ST-Link重烧原始固件。我们设计Bootloader有“紧急恢复模式”上电时长按KEY自动擦除Application区并跳转到出厂固件。4.7 Step 7量产交付 checklist血泪总结交付前必须逐项核对缺一不可项目检查方法不合格后果Bootloader CRC校验用J-Flash读取Bootloader区计算CRC32与文档比对Bootloader损坏整机变砖Application区Magic校验UART打印启动日志确认Magic OK启动失败客户投诉UDS会话超时设置CANoe注入0x10 0x03后故意延迟响应测ECU是否超时退出升级卡死需断电重启Flash擦除粒度匹配查MCU Reference Manual确认擦除命令与扇区大小一致擦除失败NRC 0x72频发OTA日志存储升级后读EEPROM确认记录了时间、版本、结果无法追溯问题责任难界定某次交付因漏查“OTA日志存储”客户升级失败后无法提供日志我们花了3天远程排查最后发现是客户自己改了EEPROM地址映射。5. 常见问题与排查技巧实录那些让工程师凌晨三点崩溃的瞬间5.1 NRC 0x78Request Correctly Received - Response Pending的真相这是OTA中最让人抓狂的NRC。表面看是“请求已收到响应待处理”实际背后有五种可能Flash擦除耗时超限ECU在0x31服务中擦除扇区若擦除时间UDS默认响应超时50ms就会发0x78。解决方案在0x31响应后ECU需在50ms内发首个0x78之后每50ms续发直到擦除完成。我们用FreeRTOS Timer实现定时发送。Security Access未完成发0x27服务获取Seed后Key计算错误ECU处于“等待Key”状态。此时发0x34ECU返回0x78而非0x33。用CANoe查看0x27响应确认Seed值是否与算法匹配。内存忙Application区正在执行任务抢占Flash总线。需在擦除前调用__disable_irq()关闭全局中断或用DMA传输减少CPU占用。CAN总线负载过高总线Busy率70%时ECU响应帧被仲裁失败。用CANoe的Bus Load监控临时关闭非关键ECU。Bootloader未就绪Bootloader刚启动未完成初始化。增加100ms启动延时或在Bootloader中加“Ready Flag”。实操技巧抓包时若连续收到多个0x78说明ECU在处理耗时操作若只收一个0x78后无响应大概率是程序卡死。此时用SWD暂停查PC指针位置。5.2 “Can not open com port”错误的硬件级溯源这个错误看似是驱动问题实则暴露底层硬件缺陷USB-CAN适配器供电不足PC USB口仅提供500mA而Peak PCAN-USB满载需350mA若同时接其他USB设备电压跌至4.75V以下适配器复位。解决方案用带外接电源的USB Hub。CAN收发器地线未共地ECU与PC通过CAN线连接但双方GND未短接形成共模电压差。用万用表测ECU外壳与PC USB金属壳间电压若1V用导线短接两地。CAN_H/CAN_L反接线序接反导致差分电压异常。用示波器测CAN_H对地电压正常应为2.5V±0.5V反接时CAN_H≈3.5VCAN_L≈1.5V。Windows驱动冲突某些USB转串口驱动如CH340会劫持CAN适配器。卸载所有串口驱动只留PCAN驱动。Linux权限问题Ubuntu下需sudo usermod -a -G dialout $USER并重启。否则can0设备无读写权限。5.3 OTA升级后ECU不启动的终极排查链这是最严重的故障按此链路逐级排查确认Bootloader是否运行用ST-Link连接读取SP堆栈指针与PC程序计数器。若SP0x20000000SRAM起始PC0x08000000Bootloader入口说明Bootloader正常。检查Vector Table读取0x0800C000处4字节应为Application的SP值如0x20001000。若为0xFFFFFFFF说明Flash写入失败。验证Application校验和读取Application区全部数据计算CRC32与ota.bin的CRC比对。不匹配则重刷。检查中断向量偏移Application的startup.s中VECT_TAB_OFFSET 0xC000若错设为0x0000启动时跳转到Bootloader区。电源纹波测试用示波器测VDD启动瞬间是否有100mV跌落。某次因电容ESR过大导致Reset引脚误触发。独家技巧在Application的Reset Handler开头插入while(1){GPIOA-BSRR GPIO_BSRR_BR0;}翻转LED若LED不亮说明未跳转到Application若亮但不执行后续说明Vector Table或时钟配置错误。5.4 CAN总线ID号的隐藏语义不止是地址CAN报文ID常被简单理解为“地址”但在UDS中它承载更多协议语义0x7DF1999诊断请求标准ID所有ECU监听此ID。若ECU ID为0x7A1它也需响应0x7DF请求这是UDS的“广播寻址”特性。0x7E0~0x7E71984~1991功能地址用于向所有ECU发同一命令如0x10 0x03。但OTA必须用物理地址0x7DF否则ECU无法区分是全局指令还是本机指令。0x7E8~0x7EF1992~1999响应IDECU用0x7E8响应0x7DF请求。若ECU ID为0x7A1其响应ID为0x7A90x7A10x08这是物理寻址的“偏移规则”。扩展帧ID29位车载常用如0x18DAF110表示“ECU IDF1功能IDDAPDU类型10”。解析时需掩码ecu_id (id 8) 0xFF。某次项目因混淆功能地址与物理地址T-Box向0x7E0发0x34所有ECU响应造成总线风暴。根源是未理解UDS寻址模型。5.5 UDS 19服务Read DTC的深度解读0x19服务常被当作“读故障码”但它在OTA中承担关键哨兵角色0x19 0x02 0x09读取当前DTC返回格式为0x59 0x02 0x09 [DTC_num] [DTC1] [DTC2]...。若DTC_num0表示无故障若DTC10x000000表示“无故障”但ECU可能因OTA失败置位U0100。0x19 0x0A读取DTC快照包含故障发生时的环境数据如电池电压、温度。OTA失败后读此快照可定位是电压不足还是温度超限。0x19 0x06清除DTC。OTA成功后必须执行否则仪表盘亮故障灯。注意清除后需发0x10 0x01切回Default Session否则ECU仍处于Extended模式。隐含状态若0x19返回NRC 0x31说明ECU诊断数据库损坏此时OTA必然失败需先修复数据库。实战案例某次升级失败0x19返回U0121CAN收发器故障我们查PCB发现CAN_L焊盘虚焊重新焊接后恢复正常。这比查代码快10倍。6. 我的个人经验从踩坑到建立标准这三年我悟出的三条铁律我在汽车电子一线做UDS OTA整整三年从第一次把客户ECU刷成“砖头”被骂哭到现在带队交付12个量产项目最大的体会不是技术多难而是认知偏差有多致命。第一条铁律永远假设ECU比你想象的更脆弱。它不是Linux服务器没有OOM Killer没有swap分区RAM就那么一点Flash擦写次数就那么几千次。我见过太多工程师用“PC思维”写嵌入式代码——开个线程等响应、malloc一堆内存、用printf打满日志结果OTA时RAM爆掉ECU复位。后来我定下死规矩所有OTA相关代码静态分析内存占用必须≤80% RAMFlash擦写操作必须加电压/温度保护日志只存关键事件到EEPROM。第二条铁律CAN总线不是通信线是生命线。它承载着刹车、转向、电池管理OTA只是它的附加功能。所以我的OTA设计永远让位于实时性UDS任务优先级必须低于CAN应用任务帧间隔必须留足仲裁时间升级过程绝不关闭其他ECU通信。曾有个项目客户要求OTA时禁用所有CAN报文结果升级中ABS模块失联差点出安全事故。现在我合同里明确写“OTA期间ECU必须维持基础CAN通信包括心跳与状态报文”。第三条铁律交付不是刷完固件是交出可追溯的证据链。每次升级Host工具自动生成报告时间戳、ECU VIN、旧/新版本号、CRC32、总帧数、失败帧ID、NRC统计。这份报告存云端客户可随时查。有次客户质疑升级失败我们30秒调出报告显示第1274帧因NRC 0x72失败定位到是他们车间电压不稳对方
返回列表