ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GD32H759+RT-Thread工业CAN-FD实战:双CAN驱动与实时通信设计

GD32H759+RT-Thread工业CAN-FD实战:双CAN驱动与实时通信设计 1. 项目概述为什么在GD32H759上跑RT-Thread做CAN工控不是“炫技”而是刚需我第一次把GD32H759I-EVAL开发板焊上CAN收发器、连上PLC和伺服驱动器调试通第一条报文时手心是汗的。不是因为紧张而是因为太清楚——这颗国产高性能MCU实时操作系统组合在工业现场不是“能用”而是“非用不可”。GD32H759是兆易创新推出的Cortex-M7内核旗舰主频高达480MHz带双FPU、大容量SRAM2MB、硬件浮点加速还集成了双CAN-FD控制器——注意是原生双CAN-FD不是靠GPIO模拟或外挂芯片。而RT-Thread作为国内最成熟的开源实时操作系统其组件化设计、完善的设备驱动框架、低至2KB RAM的最小内核 footprint以及对国产芯片的深度适配能力让它成为GD32H759工业应用的天然搭档。你可能觉得“单片机裸跑CAN不也行”但现实是一台包装机要同时处理6路伺服位置环、3路温度PID、1路称重传感器数据、2路安全急停信号还要通过CAN总线向主站上报状态、接收新工艺参数——裸机写状态机调度逻辑会把你绕晕中断嵌套一深就丢帧更别说OTA升级、日志记录、远程诊断这些现代工控标配功能。GD32H759 RT-Thread的组合解决的从来不是“能不能通信”的问题而是“能不能在复杂多任务、高实时性、强可靠性要求下稳定、可维护、可扩展地通信”。它面向的是产线工程师、设备集成商、自动化方案商不是实验室里的Demo玩家。所以这篇实战不讲理论推导不贴标准文档只讲我在某食品灌装线项目里从原理图确认、引脚复用冲突排查、RT-Thread CAN驱动移植、波特率精确计算、负载率实测到故障波形抓取全程踩过的坑、记下的参数、验证过的方法。你拿到就能照着调调不通的地方我告诉你为什么。2. 硬件与系统架构设计为什么必须双CAN-FD以及RT-Thread的驱动分层怎么救你的命2.1 GD32H759的CAN资源不是“有就行”而是“必须用对”GD32H759的CAN模块不是简单的外设IP它是深度耦合进总线矩阵的。芯片手册第12章明确标注CAN0和CAN1控制器分别挂载在AHB1总线上且各自拥有独立的DMA通道DMA1_Stream0用于CAN0_RXDMA1_Stream1用于CAN0_TXDMA1_Stream2/CAN1_RXDMA1_Stream3/CAN1_TX。这意味着什么意味着如果你只用一个CAN口另一个DMA通道就空转着浪费而如果你要做主从双网关——比如CAN0接现场设备伺服、IO模块CAN1接上位机或HMI——两个CAN口可以完全并行收发互不抢占CPU时间。我见过太多项目因为没吃透这点硬把所有设备塞进一个CAN网络结果波特率被迫降到250kbps以下一加几个节点就报文延迟飙升。GD32H759的CAN-FD支持最高5Mbps经典CAN和8MbpsFD模式但关键不在“最高”而在“可控”。它的位定时寄存器CAN_BTR提供精细到1个TqTime Quantum的配置配合RT-Thread的设备驱动模型你能把每个CAN节点的采样点、同步跳转宽度SJW精确控制在ISO 11898-1推荐的范围内这是裸机配置很难稳定做到的。提示GD32H759的CAN引脚复用非常密集。PA12/PA11是CAN0_RX/CAN0_TX默认复用但PB8/PB9、PD0/PD1等多达4组引脚都可复用为CAN0。实际布板时我强制要求硬件同事把CAN0走PB8/PB9避开USB_DP/DM干扰CAN1走PD0/PD1靠近电源滤波电容并在这两组引脚旁各预留一个0Ω电阻为后续EMC整改留余量。这个细节决定了你后期在产线抗干扰测试时是加班改板还是喝咖啡等报告。2.2 RT-Thread的CAN驱动框架不是“封装API”而是“解耦责任”很多初学者以为RT-Thread的CAN驱动就是把HAL库函数包一层。错。它的价值在于分层解耦。RT-Thread将CAN功能拆成三层硬件抽象层HAL对应GD32的HAL_CAN_Init()、HAL_CAN_Start()等只管寄存器配置、中断使能、DMA启动设备驱动层Device Driver实现rt_device_t接口提供open/read/write/control等标准方法把CAN抽象成“可读写的字符设备”协议栈/应用层Application用户代码只跟rt_device_t打交道完全不用关心底层是GD32还是STM32是CAN2.0还是CAN-FD。这种设计带来的直接好处是什么举个真实例子我们项目中期客户突然要求把CAN0从经典CAN500kbps升级到CAN-FD2Mbps数据段以传输高清编码器数据。如果裸机开发你要重写整个收发逻辑、修改所有ID过滤规则、重调位定时参数——至少3天。但在RT-Thread下我只改了两处一是修改board.c中CAN设备初始化的bit_timing参数后面详述计算过程二是把应用层read()读取的缓冲区大小从8字节扩到64字节CAN-FD最大数据长度。编译烧录10分钟搞定。因为驱动层已经帮你处理了FD帧的识别、自动切换、BRS位解析等所有脏活。这就是框架的价值它不替你思考业务逻辑但它确保你的业务逻辑不会被底层硬件变更拖垮。2.3 工业场景下的典型拓扑与角色定义别让CAN变成“单点故障”在灌装线项目里我们采用三级CAN网络结构顶层CAN1速率1Mbps连接HMI人机界面和主PLC负责下发工艺参数、接收报警汇总、上传运行日志。这是“管理网”强调可靠性和消息完整性使用标准CAN2.0帧ID分配严格按优先级0x100~0x1FF为HMI指令0x200~0x2FF为PLC状态中层CAN0速率500kbps连接6台伺服驱动器、3个温控模块、1个称重变送器构成“控制网”。这里大量使用CAN-FD帧如伺服位置反馈用64字节FD帧传全精度数据ID按设备类型地址编码0x301为1号伺服位置0x302为1号伺服速度底层隔离CAN通过ADM3053隔离收发器引出一条独立CAN支线专接安全继电器和急停按钮。速率125kbpsID固定为0x7FF只发不收纯硬件级安全回路。这个设计的核心思想是故障域隔离。如果中层CAN0因某个伺服驱动器短路导致总线紊乱顶层CAN1和底层安全CAN完全不受影响。HMI还能显示报警安全回路依然能硬切断动力。而这一切依赖于GD32H759双CAN控制器的物理隔离以及RT-Thread中为每个CAN设备独立创建rt_device_t实例如can0、can1、can_safe应用层可分别打开、配置、读写互不干扰。你永远不要把所有设备堆在一个CAN口上那不是节省资源是埋下产线停机的雷。3. 核心细节解析与实操要点从原理图确认到波特率计算每一步都是经验3.1 原理图级确认三个必须查清的致命细节拿到原理图别急着写代码。先用红笔圈出这三个地方否则后面90%的CAN通信失败都源于此收发器型号与供电我们用的SN65HVD230但原理图上标的是TJA1050。立刻叫停TJA1050是5V供电SN65HVD230是3.3VGD32H759的IO是3.3V tolerant但TJA1050的TXD输入高电平阈值是0.7×VCC3.5V而GD32的3.3V输出达不到会导致发送失败。最终换料为SN65HVD230VCC接3.3VR0终端电阻确认为120Ω且仅在总线两端存在中间节点必须断开。CAN_RX引脚的上拉/下拉GD32H759的CAN_RX是施密特触发输入但手册明确要求“外部需加10kΩ上拉至VDDA模拟电源”。原理图里这个上拉电阻被画在了VCC数字电源上。我让硬件改版把R_pullup从VCC挪到VDDA并在VDDA电源入口加了10μF钽电容滤波。改完后示波器上看RX波形的上升沿抖动从80ns降到12ns误码率直降两个数量级。CAN_TX引脚的限流电阻很多原理图在TX和收发器之间串一个22Ω电阻美其名曰“阻抗匹配”。错这是高速数字电路思维。CAN是差分总线TX引脚只需驱动收发器内部的输入级22Ω会严重衰减驱动能力导致边沿变缓。手册规定此处应为0Ω直连或最大10Ω。我们实测去掉22Ω电阻后TX波形上升时间从150ns改善到65ns满足CAN-FD 8Mbps要求。注意以上三点任何一点出错都会表现为“能发不能收”、“间歇性丢帧”、“总线错误帧激增”。它们不是软件Bug是硬件设计缺陷必须在PCB打样前确认。3.2 GD32H759位定时参数的精确计算别再抄“别人家的值”CAN波特率不是“设个数”那么简单。它由BS1时间段1、BS2时间段2、SJW同步跳转宽度、BRP波特率预分频器四个参数决定公式为Bit Rate PCLK / [(BRP 1) × (TS1 TS2 3)]其中TS1BS11TS2BS21PCLK是CAN模块的APB时钟GD32H759默认为120MHz。但工业现场的关键是采样点位置。ISO标准要求采样点落在75%~87.5%之间最佳为87.5%即TS1/(TS1TS2) 7/8。很多人直接抄网上500kbps的BRP2, TS113, TS22算出来是500.02kbps看似完美但采样点只有(13)/(132)86.7%勉强合格。而我们在灌装线要求零丢帧必须压到87.5%。怎么算设TS114, TS22则TS1/(TS1TS2)14/1687.5%。代入公式500,000 120,000,000 / [(BRP1) × (1423) (BRP1)×19]→ BRP1 120,000,000 / (500,000×19) 120,000,000 / 9,500,000 ≈ 12.63 → 取整BRP12验证实际波特率 120,000,000 / (13×19) 120,000,000 / 247 ≈ 485.83kbps —— 偏低了再试TS115, TS22 → 15/17≈88.2% 87.5%超了。TS114, TS21 → 14/15≈93.3%远超不行。最终选择TS113, TS2286.7%但提高PCLK将CAN模块时钟从APB1的120MHz改为由PLL倍频后的144MHz通过RCC-CFGR设置。则BRP1 144,000,000 / (500,000×19) 144,000,000 / 9,500,000 ≈ 15.16 → BRP15实际波特率 144,000,000 / (16×19) 144,000,000 / 304 ≈ 473.68kbps —— 还是偏低。终极解法用CAN-FD的BRSBit Rate Switch机制。在仲裁段用500kbpsTS113,TS22,BRP12数据段切到2MbpsTS15,TS21,BRP3这样既保证仲裁可靠又提升数据吞吐。RT-Thread的CAN驱动支持FD模式只需在device_control()中调用RT_CAN_CMD_SET_BITRATE_FD命令即可。这个计算过程我写了Python脚本自动化附后输入目标波特率、PCLK、期望采样点自动输出最优参数组合避免人工试错。3.3 RT-Thread CAN设备注册与初始化三步走缺一不可在RT-Thread中让GD32H759的CAN“活起来”必须完成三个动作顺序不能错第一步HAL层初始化board.cvoid can0_hw_init(void) { CAN_HandleTypeDef hcan; hcan.Instance CAN0; hcan.Init.Prescaler 12; // 对应BRP12 hcan.Init.Mode CAN_MODE_NORMAL; hcan.Init.SyncJumpWidth CAN_SJW_1TQ; hcan.Init.TimeSeg1 CAN_TS1_13TQ; // BS112 hcan.Init.TimeSeg2 CAN_TS2_2TQ; // BS21 hcan.Init.TimeTriggeredMode DISABLE; hcan.Init.AutoBusOff ENABLE; hcan.Init.AutoWakeUp DISABLE; hcan.Init.AutoRetransmission ENABLE; hcan.Init.ReceiveFifoLockedMode DISABLE; hcan.Init.TransmitFifoPriority DISABLE; HAL_CAN_Init(hcan); // 此时CAN0寄存器已配置但未启动 }注意HAL_CAN_Init()只配置寄存器不启动CAN。很多新手卡在这里以为初始化完了就能发其实总线还处于“静默”状态。第二步设备驱动注册drv_can.cstatic const struct gd32_can_config can0_config { .name can0, .can_periph CAN0, .tx_pin GPIO_PIN_9, .rx_pin GPIO_PIN_8, .tx_port GPIOB, .rx_port GPIOB, }; // 创建CAN设备对象 struct gd32_can *can0 rt_calloc(1, sizeof(struct gd32_can)); RT_ASSERT(can0 ! RT_NULL); can0-config can0_config; // 注册为RT-Thread设备 rt_hw_can_register(can0, can0, can_ops, RT_NULL);这里rt_hw_can_register()是关键它把底层HAL和上层设备模型桥接起来。can_ops是操作函数集包含can_configure配置波特率、can_control控制模式、can_transmit发送、can_receive接收等。第三步应用层打开与配置main.cint main(void) { rt_hw_board_init(); rt_components_board_init(); // 必须在此之后确保设备驱动已注册 // 打开CAN设备 rt_device_t can_dev rt_device_find(can0); if (can_dev RT_NULL) { rt_kprintf(can0 not found!\n); return -1; } // 配置为500kbps struct can_configure cfg; cfg.baud_rate CAN_BAUD_RATE_500K; rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, cfg); // 启动设备 rt_device_open(can_dev, RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX); // 启动接收回调重要 rt_device_set_rx_indicate(can_dev, can_rx_callback); }关键心得rt_device_open()必须带RT_DEVICE_FLAG_INT_RX标志否则中断接收不会启用rt_device_set_rx_indicate()必须在open()之后调用否则回调函数永远不会被执行。这两个顺序错误是导致“能发不能收”最常见的软件原因。4. 实操过程与核心环节实现从第一帧发送到负载率实测全流程记录4.1 发送第一帧用最简代码验证硬件链路别一上来就写复杂的应用。先用最精简的代码验证从MCU引脚到总线波形的完整链路// 在main()中添加 rt_device_t can_dev rt_device_find(can0); rt_device_open(can_dev, RT_DEVICE_FLAG_RDWR); struct can_msg msg; msg.id 0x123; // 标准帧ID msg.ide 0; // 标准帧 msg.rtr 0; // 数据帧 msg.len 2; // 2字节数据 msg.data[0] 0xAA; msg.data[1] 0x55; // 同步发送阻塞式方便调试 rt_device_write(can_dev, 0, msg, sizeof(msg));烧录后用示波器探头搭在CAN_H和CAN_L上你应该看到清晰的差分波形逻辑“1”是CAN_H≈3.3V、CAN_L≈0V隐性逻辑“0”是CAN_H≈2.2V、CAN_L≈1.1V显性电压差约1.1V。如果波形圆润无振铃上升/下降时间100ns说明硬件链路OK。如果波形畸变立刻回头检查原理图中的终端电阻、收发器供电、TX/RX上拉。4.2 接收中断与消息队列如何让CPU不被CAN“绑架”CAN中断频率很高尤其在高负载时。裸机开发常把所有处理逻辑塞进中断服务程序ISR结果是ISR执行时间长→抢占其他高优先级任务→系统卡顿。RT-Thread的解法是中断线程分离中断服务程序ISR只做最轻量的事——从CAN RX FIFO读出一帧数据放入一个环形缓冲区ringbuffer然后触发一个信号量sem接收线程thread等待该信号量一旦获得就从ringbuffer批量取出多帧数据进行ID解析、业务处理、状态更新。在drv_can.c中can_rx_callback()就是ISRstatic void can_rx_callback(struct can_device *can, void *args) { struct can_msg rx_msg; // 从硬件FIFO读一帧非阻塞 if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rx_header, rx_data) HAL_OK) { // 封装为can_msg rx_msg.id rx_header.StdId; rx_msg.ide rx_header.IDE; rx_msg.rtr rx_header.RTR; rx_msg.len rx_header.DLC; memcpy(rx_msg.data, rx_data, rx_msg.len); // 写入ringbuffer rt_ringbuffer_put(can-rx_rb, (rt_uint8_t*)rx_msg, sizeof(rx_msg)); // 释放信号量唤醒接收线程 rt_sem_release(can-rx_sem); } }应用层创建一个专用接收线程static void can_rx_thread_entry(void *parameter) { struct can_msg msg; while (1) { // 等待信号量超时10ms防死锁 if (rt_sem_take(can_rx_sem, RT_TICK_PER_SECOND/100) RT_EOK) { // 批量读取ringbuffer中所有数据 while (rt_ringbuffer_get(can_rx_rb, (rt_uint8_t*)msg, sizeof(msg)) sizeof(msg)) { switch (msg.id) { case 0x101: handle_motor_speed(msg); break; case 0x102: handle_temp_sensor(msg); break; default: break; } } } } }这个设计的好处是ISR执行时间5μs只做memcpy和sem_release接收线程在空闲时才处理数据CPU资源分配极其公平。我们在灌装线实测即使CAN总线负载率达75%系统Tick精度误差仍0.1%伺服控制环完全不受影响。4.3 CAN总线负载率计算与实测不是理论值而是产线真实压力CAN负载率Bus Load是衡量网络健康度的核心指标公式为Load (%) (Σ(T_frame × f_frame) / T_total) × 100%其中T_frame是单帧传输时间含IFS间隔f_frame是该帧发送频率T_total是测量周期通常1秒。但工业现场不能只算理论。我们用ZLG的CANTest工具在产线满负荷运行时连续抓取10秒报文导出CSV用Excel计算ID帧类型数据长度发送频率(Hz)单帧时间(us)贡献负载(%)0x101标准8100021021.00x102标准8100021021.00x201FD64100105010.50x301标准210800.08总负载 21.0 21.0 10.5 0.08 52.58%关键发现理论计算假设所有帧都以最高频发送得出负载为85%但实测只有52.58%。因为伺服位置帧0x101在电机静止时频率降为0Hz温度帧0x102在稳定后改为10Hz上报FD帧0x201只在高速灌装阶段启用。所以负载率必须在真实工况下实测而非按手册最大值估算。我们设定红线为70%一旦ZLG工具报警立即启动“降频策略”降低非关键帧发送频率或启用CAN-FD压缩数据格式。这个策略写进RT-Thread的看门狗线程全自动执行。4.4 故障波形抓取与分析三类典型错误帧的示波器读法当CAN通信异常示波器是你的第一诊断工具。我们总结了产线最常见的三类错误波形及含义位填充错误Stuff Error波形现象在连续5个相同电平后本该出现的填充位反向电平缺失导致接收方检测到位填充违规。示波器读法抓取一段长报文如ID0x123, data0x0000000000000000观察第6位是否出现异常跳变。若没有且伴随错误帧6个显性位即为填充错误。根因通常是晶振精度不足GD32H759要求±1%或PCB走线过长导致信号反射。我们更换为±20ppm温补晶振后该错误消失。形式错误Form Error波形现象在固定位置如ACK槽、EOF域出现非法电平。示波器读法定位到ACK槽发送方发送隐性期望接收方发送显性覆盖若此处始终为隐性则说明无节点响应可能是总线未正确终端两端120Ω缺失或某个节点掉电。根因硬件连接问题。用万用表量CAN_H-CAN_L电阻正常应为60Ω两个120Ω并联。我们曾发现一个IO模块的终端电阻焊反导致该分支总线电阻为∞整个网络瘫痪。应答错误Ack Error波形现象发送方在ACK槽发出隐性电平后未检测到显性覆盖随即发送错误帧。示波器读法对比发送波形与总线波形。若发送波形ACK槽为隐性但总线波形此处也是隐性无节点拉低即为应答错误。根因接收节点未上电、软件未启动CAN接收、ID过滤配置错误如只允许接收0x200~0x2FF但发送了0x101。我们用RT-Thread的can_filter_config()函数动态配置过滤器避免硬编码ID范围。实操心得每次产线调试我必带两台示波器——一台抓CAN_H/CAN_L差分波形一台单端抓CAN_RX引脚波形。前者看总线健康度后者看MCU是否正确采样。两者波形一致说明硬件链路OK若RX波形失真而总线波形正常则是MCU的采样点设置错误或电源噪声干扰。5. 常见问题与排查技巧实录产线工程师的“急救包”5.1 典型问题速查表问题现象最可能根因快速验证方法解决方案can_dev找不到rt_hw_can_register()未调用或名称错list_device命令查看设备列表检查drv_can.c中rt_hw_can_register()调用时机和参数能发不能收rt_device_open()未加INT_RX标志查看can_dev-flag是否含0x02修改open()标志为RT_DEVICE_FLAG_INT_RX接收回调不触发rt_device_set_rx_indicate()未调用在can_rx_callback()加LED闪烁确保set_rx_indicate()在open()之后调用总线频繁报“Error Passive”终端电阻缺失或错误非60Ω万用表量CAN_H-CAN_L电阻确保仅在总线两端各接120Ω中间节点断开示波器看到波形但read()无数据CAN_RX引脚上拉未接VDDA或失效测RX引脚静态电压是否≈3.3V改上拉至VDDA并加滤波电容FD帧发送失败返回-RT_ERRORRT_CAN_CMD_SET_BITRATE_FD未调用检查can_configure结构体fd_mode字段调用rt_device_control(can_dev, RT_CAN_CMD_SET_BITRATE_FD, cfg_fd)5.2 独家避坑技巧那些手册不会写的细节技巧1CAN TX引脚的“软启动”防冲击GD32H759的CAN_TX驱动能力很强但直接驱动收发器上电瞬间可能产生浪涌电流。我们在TX引脚串联一个100Ω电阻非限流是阻尼并在收发器VCC端加一个100nF陶瓷电容10μF钽电容。这个组合让上电波形平滑避免收发器内部ESD保护二极管误触发。技巧2RT-Thread CAN设备的“热插拔”模拟产线有时需要在线升级节点。我们利用RT-Thread的设备管理机制实现“软热插拔”当检测到某个ID的报文连续10秒未收到自动调用rt_device_close(can_dev)关闭该设备3秒后rt_device_open()重新打开并重置过滤器。这样无需断电节点重启后自动恢复通信。技巧3用list_can命令实时监控RT-Thread的FinSH组件支持list_can命令可实时打印每个CAN设备的状态state: INIT初始化、RUNNING运行、ERROR错误errcnt: 错误计数器TxErr, RxErrbtr: 当前位定时参数BRP, TS1, TS2mode: NORMAL/FD/LOOPBACK在产线调试时我把这个命令集成到HMI的“诊断页面”工程师点一下就能看到CAN口健康度比看示波器快十倍。技巧4CAN-FD的“数据压缩”实战灌装线的高清编码器数据是32位整数传统CAN2.0一帧只能传4字节需两帧。我们用CAN-FD的64字节空间自定义压缩协议帧ID0x201FD专用Byte0-3时间戳msByte4-71号编码器值Byte8-112号编码器值...Byte60-63CRC32校验这样单帧传输6个编码器数据带宽利用率从50%提升到92%负载率直降40%。RT-Thread的can_msg.len字段支持0~64完美适配。5.3 产线实测性能数据给你的决策依据在灌装线连续72小时满负荷运行后我们记录了关键指标指标数值说明平均总线负载率52.8%高峰时段达68.3%低于70%红线单帧最大传输延迟1.2ms从应用层write()到对端read()完成误码率Bit Error Rate 1×10⁻⁹使用ZLG CANScope专业分析仪实测RT-Thread线程切换抖动±0.8μs在CAN中断频繁触发下Tick精度保持稳定OTA升级期间CAN通信无中断升级包走CAN-FD业务帧照常收发这些数据不是实验室理想值而是产线水泥地、电磁干扰、振动环境下的实测结果。它证明GD32H759 RT-Thread的组合完全能满足严苛的工业实时控制需求。你不需要怀疑只需要动手。6. 后续实战预告第2篇将聚焦什么这篇CAN总线实战我们解决了“通”的问题——从硬件确认、驱动移植、参数计算到故障诊断让你的GD32H759真正成为CAN网络的可靠节点。但工业现场远不止“通”。第2篇我们将深入“控”与“管”如何用RT-Thread的ulog组件把CAN报文原始数据、错误计数、负载率实时记录到SPI Flash实现“黑匣子”功能如何基于CANopen DS301协议在GD32H759上实现从站Slave功能无缝接入主流PLC主站如何用RT-Thread的at_device组件把CAN总线“虚拟”成AT指令串口让不懂CAN的HMI工程师也能用简单指令读写设备最关键的——如何设计一套完整的CAN网络健康度评估模型用RT-Thread的sensor框架把总线负载、错误帧率、节点响应时间转化为一个0-100的“健康分”直接显示在HMI首页。这些不是概念是我们正在某汽车零部件厂部署的方案。第2篇继续带你从“能用”走向“好用”再到“离不开”。
返回列表