ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

蓝桥杯国赛DHT11温湿度传感器驱动:从时序原理到稳定集成实战

蓝桥杯国赛DHT11温湿度传感器驱动:从时序原理到稳定集成实战 1. 项目概述从国赛真题到传感器实战最近几年带学生备赛蓝桥杯发现国赛阶段对温湿度传感器的考察越来越“刁钻”。它不再是简单让你读个数、显示一下而是会结合定时器、状态机、通信协议甚至低功耗设计来出题。很多同学在省赛阶段靠着例程和模板能过关一到国赛面对DHT11这类单总线传感器就手忙脚乱时序抓不准数据读不对更别提在复杂的多任务环境中稳定运行了。这其实暴露了一个核心问题对传感器底层驱动原理的理解不够透彻仅仅停留在“调用库函数”的层面。这份学习笔记就是针对这个痛点来的。它不只是一份DHT11的数据手册翻译而是我结合多年辅导和实际项目经验从国赛真题的考察角度出发拆解单总线温湿度传感器的核心原理、驱动实现、常见陷阱以及高阶应用。无论你是正在备战国赛的选手还是刚接触单片机、想弄明白这种“一根线”怎么既能发命令又能传数据的爱好者这份笔记都能帮你把这块硬骨头啃下来。我们会从最基础的时序波形图开始手把手教你用示波器或者没有示波器时用IO翻转和延时来调试一直讲到如何把它集成到一个有按键、显示、通信的完整国赛项目中并保持稳定可靠。2. 核心需求解析国赛究竟在考什么要学好温湿度传感器尤其是应对蓝桥杯国赛首先得明白出题人的意图。国赛的题目往往具有综合性、工程性和一定的迷惑性。对于温湿度传感器模块其考察重点可以归纳为以下几个层面理解了这些你的学习才能有的放矢。2.1 底层时序的精确实现能力这是最基础也最容易失分的地方。DHT11使用的是单总线协议这意味着数据收发共用一根数据线完全依靠精确的时序来区分“0”、“1”和通信阶段。国赛题目可能不会直接给你示波器截图但会通过程序运行现象如读数全为0、读数偶尔跳动、传感器无响应来间接考察你对时序的理解。核心考点在于起始信号单片机拉低总线至少18ms然后拉高20-40us等待传感器响应。这里的时间要求是“至少”和“范围”如果拉低时间不够传感器可能不响应如果拉高后等待时间不对可能错过传感器的响应信号。数据位读取传感器输出的每个数据位都以一个50us的低电平起始随后的高电平持续时间决定位值26-28us表示‘0’70us表示‘1’。这里的关键是如何在单片机中区分这两种时长。很多同学用简单的delay_us(30)然后读电平来判断这种方法在系统主频变化或中断干扰下极易出错。国赛期望你使用更稳健的方法比如在起始低电平后开启一个定时器或循环计数直接测量高电平的持续时间。为什么这很重要因为在实际的嵌入式系统中延时函数delay_us()的准确性受编译器优化、中断打断等因素影响很大。一个优秀的、能够应对国赛的驱动必须采用不依赖于绝对延时的、基于状态或时间戳的测量方法。2.2 在复杂系统中保持驱动稳定性的能力省赛可能让你单独读一个传感器。但国赛项目中温湿度传感器往往只是系统的一个组成部分。你的程序可能还要处理按键扫描、数码管或LCD动态显示、串口通信、EEPROM存储等任务。这些任务都会占用CPU时间可能产生中断从而打断传感器脆弱的时序通信。核心考点在于通信过程的原子性保护在启动传感器通信到完整接收40位数据期间必须保证时序不被其他中断特别是定时器中断、串口中断打断。常见的做法是在读取传感器数据前关闭全局中断读完后再打开。但这需要你对中断体系有清晰的认识。超时与错误处理机制你的驱动不能假设每次通信都能成功。如果传感器没接好、时序稍有偏差、受到干扰程序应该能检测到例如响应超时、数据校验和错误并返回一个明确的错误码而不是死等或者返回一个错误的数据。健壮的错误处理是区分普通代码和竞赛级代码的关键。资源冲突管理DHT11的数据线通常与其它器件比如LED、按键复用IO口。在初始化、读写前后需要正确配置IO口的工作模式推挽输出、开漏输出、上拉输入。在复杂的硬件连接中这需要仔细查看原理图并规划IO状态。2.3 数据处理的综合应用能力国赛不会满足于你仅仅把温湿度的数值读出来。它可能会要求你将数据进行转换、滤波、显示、判断或传输。核心考点可能包括数据格式转换与显示DHT11读出的湿度整数/小数、温度整数/小数以及校验和是5个独立的字节。你需要将它们组合成有意义的数值并可能转换成字符串通过数码管、LCD或串口发送出去。这里涉及字节操作、数值到字符串的转换如sprintf函数的使用。简单滤波算法为了防止显示值跳动可能需要实现简单的软件滤波例如连续读取N次去掉最大最小值后取平均或者采用一阶滞后滤波。逻辑判断与控制根据读取的温湿度值控制继电器、风扇、蜂鸣器等执行机构。例如温度超过阈值报警湿度低于阈值启动加湿器。这考察了你将传感器数据与实际控制逻辑结合的能力。理解以上三点你就知道学习DHT11不能止步于“跑通例程”。接下来我们将深入其硬件和协议层这是写出稳定驱动的基础。3. DHT11传感器深度剖析硬件与协议要驾驭它必须先了解它。DHT11虽然价格低廉、接口简单但其内部的逻辑和通信协议却包含了许多值得琢磨的细节。3.1 硬件接口与电气特性DHT11通常有4个引脚也有3引脚封装VCC供电引脚范围3.3V-5.5V。蓝桥杯竞赛平台通常是5V供电直接连接即可。GND电源地。DATA双向单总线数据线。这是通信的核心。NC空脚悬空不接。关键硬件细节上拉电阻数据线DATA必须连接一个4.7KΩ - 10KΩ的上拉电阻到VCC。这个电阻至关重要它保证了在总线空闲时即单片机和传感器都不主动拉低总线时总线能被上拉到高电平。很多同学自己焊接模块时忘记了这个电阻导致通信一直失败。蓝桥杯官方提供的集成开发平台上这个电阻通常已经设计在电路板上了但如果你自己用分立元件连接务必记得加上。IO口模式因为DATA线是双向的单片机IO口需要在不同时刻切换模式。单片机发送起始信号时需要将IO配置为推挽输出模式并强行拉低和拉高总线。单片机等待传感器响应和接收数据时需要将IO配置为上拉输入或浮空输入模式外部已有上拉电阻所以浮空也可以读取传感器输出的电平。注意在STM32的HAL库中切换IO模式HAL_GPIO_WritePin输出和HAL_GPIO_ReadPin输入本身会有少量指令周期延时。在极端精密的时序要求下这个延时可能需要考虑。更常见的做法是将IO始终设置为开漏输出模式并通过操作输出数据寄存器拉低和读取输入数据寄存器释放总线由上拉电阻拉高来模拟双向总线这样可以避免模式切换的延时。但在蓝桥杯常用的51或STM32G431平台上标准输入输出切换通常已满足要求。3.2 单总线通信协议时序精讲协议是整个驱动的灵魂。我们结合时序图把每个阶段掰开揉碎讲清楚。一次完整的数据传输分为三个部分单片机发起起始信号 - 传感器响应 - 传感器发送40位数据。3.2.1 起始信号单片机→传感器单片机将DATA线拉低至少18毫秒ms。这个时间要求是“至少”我一般会拉低20ms左右留有余量。然后单片机释放总线设置为输入模式或输出高电平由上拉电阻将总线拉高。单片机需要等待20-40微秒us在这个窗口期内传感器会做出响应。这里有一个极易出错的地方单片机释放总线后需要等待一小段时间通常1-2us让总线被上拉电阻真正拉到高电平然后再开始计时等待传感器响应。如果释放后立即检测可能会读到低电平导致误判。3.2.2 传感器响应信号传感器检测到起始信号后会将总线拉低约80us作为应答。接着传感器会将总线拉高约80us通知单片机“我准备好了数据马上要来”。至此握手完成。接下来开始传输数据位。3.2.3 数据位格式传感器→单片机每一位数据都以一个约50us的低电平作为起始位。随后是一个高电平这个高电平的持续时间决定了这一位是‘0’还是‘1’‘0’高电平持续时间约为26-28us。‘1’高电平持续时间约为70us。40位数据包括16位湿度数据整数小数 16位温度数据整数小数 8位校验和。注意DHT11的精度小数部分通常读出为0。如何可靠地判断位值这是驱动编写的核心难点。最业余的方法是等待50us低电平结束后延时一个固定时间比如30us然后去读总线电平高就是‘1’低就是‘0’。这种方法极不可靠因为延时不准且容易受干扰。竞赛推荐的方法测量高电平脉冲宽度。检测到起始低电平约50us结束即总线变为高电平的那一刻开始计时。持续检测总线电平直到它再次变为低电平即下一位的开始或者等待超时。计算从高电平开始到结束的时间长度t。判断如果t在 20-40us 范围内可认为是‘0’如果t在 60-80us 范围内可认为是‘1’。如何实现高精度计时方法一使用硬件定时器。在检测到上升沿时清零并启动一个定时器配置为微秒级然后轮询等待下降沿读取定时器值。这是最准确的方法。方法二软件计数。在没有空闲定时器或追求极简代码时如51单片机可以用一个空循环来计数。首先校准出循环一次大约是多少微秒通过示波器或已知延时函数反推然后在需要计时的地方进行循环计数。虽然精度稍差但用于区分26us和70us是绰绰有余的且不受中断影响前提是测量期间关中断。3.3 数据格式与校验传感器发送的40位数据按顺序解析如下Byte0: 湿度整数部分 (Humidity High Byte)Byte1: 湿度小数部分 (Humidity Low Byte) - DHT11通常为0Byte2: 温度整数部分 (Temperature High Byte)Byte3: 温度小数部分 (Temperature Low Byte) - DHT11通常为0Byte4: 校验和 (Checksum)校验和计算Checksum Byte0 Byte1 Byte2 Byte3。注意这个和是取低8位即(Byte0Byte1Byte2Byte3) 0xFF。接收完5个字节后必须计算前4个字节的和并与接收到的第5个字节校验和进行比较。如果相等说明数据在传输过程中没有出错数据有效如果不相等必须丢弃这次的数据并重新读取。绝对不能忽略校验步骤这是保证数据可靠性的最后一道关卡。4. 驱动程序设计从基础到竞赛级理解了协议我们就可以动手编写驱动程序了。我将给出两个版本的代码示例一个基于51单片机适用于蓝桥杯传统单片机赛道的简单实现和一个基于STM32 HAL库适用于嵌入式赛道的增强稳健版。我们会重点讲解后者因为它包含了更多工程化的考量。4.1 基础版51单片机示例查询式这个版本使用简单的延时和循环计数逻辑清晰适合理解原理。但在有中断的系统中不稳定。// 假设 DHT11_DATA 连接到 P2^0 sbit DHT11_DATA P2^0; typedef struct { u8 humi_int; // 湿度整数 u8 humi_frac; // 湿度小数 u8 temp_int; // 温度整数 u8 temp_frac; // 温度小数 u8 check_sum; // 校验和 } DHT11_Data; bit DHT11_Read(DHT11_Data *dat) { u8 i, j; u8 buf[5] {0}; u8 checksum; // 1. 主机发起起始信号 DHT11_DATA 0; // 拉低 Delay_ms(20); // 拉低至少18ms DHT11_DATA 1; // 释放总线 Delay_us(30); // 等待20-40us // 2. 检测传感器响应 if(DHT11_DATA 1) return 0; // 传感器未响应 while(DHT11_DATA 0); // 等待80us低电平结束 while(DHT11_DATA 1); // 等待80us高电平结束 // 3. 接收40位数据 for(i0; i5; i) { for(j0; j8; j) { while(DHT11_DATA 0); // 等待50us低电平起始位过去 Delay_us(40); // 延时40us后采样 buf[i] 1; // 左移一位为新数据位腾出位置 if(DHT11_DATA 1) { buf[i] | 0x01; // 读到高电平记为1 while(DHT11_DATA 1); // 等待高电平结束如果是1高电平较长 } // 如果是0高电平很短此时已经为低电平直接进入下一位循环 } } // 4. 校验数据 checksum buf[0] buf[1] buf[2] buf[3]; if(checksum ! buf[4]) return 0; // 校验失败 // 5. 数据赋值 dat-humi_int buf[0]; dat-humi_frac buf[1]; dat-temp_int buf[2]; dat-temp_frac buf[3]; dat-check_sum buf[4]; return 1; // 读取成功 }这个版本的缺陷大量使用了Delay_us()和while循环等待电平变化。如果系统中有中断发生打断了这些精确延时或等待通信极大概率会失败。它仅适用于单任务、无中断的简单场景。4.2 竞赛级STM32 HAL库增强版定时器测量这个版本采用定时器测量脉冲宽度并加入了超时检测、错误处理和临界区保护适合用于真实的竞赛项目。// dht11.h #ifndef __DHT11_H #define __DHT11_H #include main.h #define DHT11_OK 0 #define DHT11_ERROR_NO_RESPONSE -1 #define DHT11_ERROR_TIMEOUT -2 #define DHT11_ERROR_CHECKSUM -3 typedef struct { uint8_t humidity; uint8_t temperature; } DHT11_DataTypedef; // 用户需要根据实际连接修改此宏 #define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 // 函数声明 void DHT11_Init(void); int8_t DHT11_Read(DHT11_DataTypedef *data); #endif// dht11.c #include dht11.h #include tim.h // 假设使用一个基本定时器如TIM6已配置为1us计数 // 微秒级延时基于SysTick或定时器此处略去实现 static void delay_us(uint16_t us) { // 使用HAL_Delay或定时器实现注意HAL_Delay是ms级 // 这里为示例实际需用定时器实现us延时 uint32_t tickstart HAL_GetTick(); while((HAL_GetTick() - tickstart) (us / 1000)); } // 设置引脚为输出模式推挽输出 static void DHT11_Set_Output(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); } // 设置引脚为输入模式上拉输入 static void DHT11_Set_Input(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin DHT11_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, GPIO_InitStruct); } // 读取引脚电平 static uint8_t DHT11_Read_Pin(void) { return HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN); } // 写入引脚电平 static void DHT11_Write_Pin(uint8_t state) { HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, state); } // 等待引脚变为指定状态带超时单位us static int8_t DHT11_Wait_State(uint8_t state, uint32_t timeout_us) { uint32_t tickstart HAL_GetTick(); uint32_t timeout_ms (timeout_us 999) / 1000; // 将us超时转换为ms向上取整 while (DHT11_Read_Pin() ! state) { if ((HAL_GetTick() - tickstart) timeout_ms) { return DHT11_ERROR_TIMEOUT; } } return DHT11_OK; } // 测量高电平脉冲宽度单位us带超时 static int32_t DHT11_Measure_Pulse(uint32_t timeout_us) { uint32_t start_tick, end_tick; uint32_t timeout_ms (timeout_us 999) / 1000; // 等待低电平确保从低电平开始测量高电平 if (DHT11_Wait_State(GPIO_PIN_RESET, timeout_us) ! DHT11_OK) { return -1; } // 等待上升沿低-高 if (DHT11_Wait_State(GPIO_PIN_SET, timeout_us) ! DHT11_OK) { return -1; } start_tick HAL_GetTick(); // 等待下降沿高-低 if (DHT11_Wait_State(GPIO_PIN_RESET, timeout_us) ! DHT11_OK) { return -1; } end_tick HAL_GetTick(); // 计算持续时间毫秒并转换为微秒近似 // 注意HAL_GetTick()精度为1ms对于几十us的脉冲测量不准 // 这里仅作流程演示。实际必须用微秒级定时器 return (int32_t)((end_tick - start_tick) * 1000); } // 核心读取函数使用微秒定时器版本 int8_t DHT11_Read(DHT11_DataTypedef *data) { uint8_t buf[5] {0}; uint8_t i, j; uint32_t pulse_width; uint8_t checksum; // --- 临界区开始关闭中断防止通信过程被打断 --- __disable_irq(); // 1. 主机发起起始信号 DHT11_Set_Output(); DHT11_Write_Pin(GPIO_PIN_RESET); // 拉低 HAL_Delay(20); // 拉低至少18ms使用HAL_Delayms级 DHT11_Write_Pin(GPIO_PIN_SET); // 释放总线 delay_us(30); // 等待20-40us使用us级延时 // 2. 切换为输入模式等待传感器响应 DHT11_Set_Input(); // 等待传感器拉低应答 (80us) if (DHT11_Wait_State(GPIO_PIN_RESET, 100) ! DHT11_OK) { // 100us超时 __enable_irq(); return DHT11_ERROR_NO_RESPONSE; } // 等待传感器拉高 (80us) if (DHT11_Wait_State(GPIO_PIN_SET, 100) ! DHT11_OK) { __enable_irq(); return DHT11_ERROR_NO_RESPONSE; } // 3. 接收40位数据 for (i 0; i 5; i) { for (j 0; j 8; j) { // 等待每一位开始的50us低电平 if (DHT11_Wait_State(GPIO_PIN_RESET, 70) ! DHT11_OK) { // 略大于50us __enable_irq(); return DHT11_ERROR_TIMEOUT; } // 等待低电平结束上升沿 if (DHT11_Wait_State(GPIO_PIN_SET, 70) ! DHT11_OK) { __enable_irq(); return DHT11_ERROR_TIMEOUT; } // 关键测量高电平持续时间 // 这里需要启动一个微秒定时器并测量 // 假设我们有一个函数 read_micros() 返回当前微秒数 uint32_t start_us read_micros(); // 获取开始时间 if (DHT11_Wait_State(GPIO_PIN_RESET, 100) ! DHT11_OK) { // 等待高电平结束 __enable_irq(); return DHT11_ERROR_TIMEOUT; } uint32_t end_us read_micros(); // 获取结束时间 pulse_width end_us - start_us; buf[i] 1; // 左移一位 // 根据脉冲宽度判断是0还是1 if (pulse_width 40) { // 阈值可以取40us左右介于26-28和70之间 buf[i] | 0x01; // 高电平时间长是1 } // 否则是0不需要操作 } } // --- 临界区结束通信完成打开中断 --- __enable_irq(); // 4. 校验数据 checksum buf[0] buf[1] buf[2] buf[3]; if (checksum ! buf[4]) { return DHT11_ERROR_CHECKSUM; } // 5. 数据赋值DHT11小数部分通常为0这里只取整数部分 >#define FILTER_SIZE 5 uint8_t temp_buffer[FILTER_SIZE] {0}; uint8_t humi_buffer[FILTER_SIZE] {0}; uint8_t buffer_index 0; // 在每次成功读取数据后调用 void DHT11_Data_Filter(DHT11_DataTypedef *new_data) { temp_buffer[buffer_index] new_data-temperature; humi_buffer[buffer_index] new_data-humidity; buffer_index (buffer_index 1) % FILTER_SIZE; uint16_t temp_sum 0, humi_sum 0; for(int i0; iFILTER_SIZE; i) { temp_sum temp_buffer[i]; humi_sum humi_buffer[i]; } filtered_temperature temp_sum / FILTER_SIZE; filtered_humidity humi_sum / FILTER_SIZE; }显示优化不要每次滤波结果变化都刷新整个显示屏。可以设置一个“显示值”变量只有当滤波后的结果与当前显示值的差值超过一定阈值例如1度或1%RH时才去更新屏幕这样可以减少不必要的刷新让显示更稳定。5.3 与国赛其他模块的协同与按键扫描协同确保按键扫描的中断或查询频率足够高通常5-10ms。如果使用上述状态机方法传感器读取在定时器中断中只占一小段时间不会影响按键的实时性。与显示模块协同将更新显示的任务放在主循环。当传感器数据就绪标志data_ready被置位时主循环用滤波后的数据去更新LCD或数码管的显示缓冲区。与串口通信协同如果需要通过串口上报数据切忌在中断服务程序如定时器中断中直接使用HAL_UART_Transmit这类可能阻塞的函数。正确的做法是在中断里将数据填入一个发送缓冲区并设置一个发送标志。在主循环中检查该标志调用串口发送函数。或者使用DMA直接存储器访问来发送彻底解放CPU。与EEPROM存储协同如果需要在断电后保存阈值通常只在阈值被按键修改时才写入EEPROM。EEPROM写入速度慢ms级且寿命有限务必避免在每次读取传感器后都进行写入操作。6. 调试技巧与常见问题排查即使代码逻辑正确在实际硬件调试中也可能遇到各种问题。这里分享一些“踩坑”后总结的经验。6.1 没有示波器如何调试时序这是学生党最常遇到的问题。没有示波器看波形怎么知道时序对不对“IO翻转延时”法在驱动代码的关键位置增加一个用于调试的GPIO引脚翻转操作。例如在起始信号拉低前、拉高后在等待传感器响应前、后在读取每一位数据开始和结束时都让这个调试引脚翻转一次。用逻辑分析仪如果也没有可以用另一个单片机的输入捕获功能或者甚至用手机慢动作拍摄LED闪烁观察这个调试引脚的电平变化。通过测量调试引脚高/低电平的持续时间可以间接推算出数据引脚上的时序是否满足要求。比如你发现“起始信号低电平”对应的调试脉冲宽度只有10ms那就说明你的延时不够18ms。6.2 常见问题速查表问题现象可能原因排查思路与解决方案始终返回错误无响应/超时1. 硬件连接错误VCC/GND接反或接触不良2. 上拉电阻未接或损坏3. 起始信号时间不够4. 等待传感器响应时间不对1. 用万用表检查电源和地。2. 确认DATA线有4.7K-10K上拉到VCC。3. 增加起始信号拉低时间到25ms试试。4. 单片机释放总线后增加一个2-5us的微小延时再开始检测传感器响应。偶尔能读成功大部分时间失败1. 时序被系统中断打断2. 电源噪声干扰3. 总线过长或受到干扰1.最重要在DHT11_Read函数开始处关闭全局中断结束前打开。2. 在VCC和GND之间靠近传感器引脚处并联一个100nF的瓷片电容。3. 缩短连接线或使用屏蔽线。数据校验经常失败1. 读取数据位的时序判断不准确2. 中断干扰导致某一位数据读错3. 传感器本身可能不稳定1. 优化位值判断逻辑用测量脉冲宽度代替固定延时采样。2. 同样检查并关闭中断。3. 尝试更换一个传感器。读出的温湿度值固定不变或为01. 数据解析逻辑错误字节顺序2. 驱动根本没有成功读取到数据但未做错误检查使用了默认值或旧值3. 传感器损坏1. 检查buf[]数组下标与湿度、温度字节的对应关系。2. 确保驱动函数有返回值检查只有成功时才更新显示数据。3. 更换传感器测试。在复杂项目中读取传感器导致其他功能如显示卡顿1. 在主循环中同步调用读取函数阻塞时间过长2. 读取函数内部使用了长延时且未释放CPU1. 采用5.1节的状态机异步读取方案将耗时操作拆解。2. 确保在等待传感器响应的循环中没有完全死等应有超时退出机制。6.3 高级调试逻辑分析仪抓包如果条件允许一个几十块钱的简易逻辑分析仪配合上位机软件是调试单总线、I2C、SPI等协议的利器。将探针连接到DATA线设置好采样率可以清晰地看到起始信号、响应信号以及每一位数据的波形。你可以直接测量高电平脉冲的宽度精确验证你的驱动判断逻辑是否正确。这是从根本上解决问题最高效的方法。最后关于DHT11的精度和响应速度要有合理的预期。它是一款廉价的慢速传感器不适合需要高速、高精度测量的场合。在蓝桥杯竞赛中理解并稳定地驱动它完整地实现数据采集、处理、显示和控制的闭环才是考察的重点。把上面的原理吃透代码调试稳定国赛中关于温湿度传感器的这部分分数你就能稳稳地拿在手里了。
返回列表