ARTICLE DETAIL

资讯详情

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

STM32内部基准电压法实现低成本锂电池电量监测,无需库仑计

STM32内部基准电压法实现低成本锂电池电量监测,无需库仑计 做电池供电产品最烦的一件事就是电量显示不准。早几年我拿到这类需求脑子里第一反应就是上库仑计ICBQ27441、MAX17048这类电量计芯片用了一堆结果发现小批量做产品的时候成本扛不住调试标定也折腾人。后来我把思路换了一下不额外加库仑计芯片直接用STM32内部基准电压配合12位ADC把锂电池电压测准再用电压-电量曲线反推出剩余电量。实测下来只要电路和软件处理得当常温下静置状态的电量误差可以控制在±5%以内而对很多智能台灯、鱼缸控制器、低成本监测设备、毕设项目来说这个精度完全够用。这篇文章就把这个方案从头到尾拆开讲包括为什么可以不用库仑计、内部基准校准的原理、分压电路怎么选电阻、完整代码怎么写、实际调试会遇到哪些坑。代码部分我贴的是HAL库的写法用标准库的朋友对照着改寄存器配置也不难。1. 先搞清楚为什么不用库仑计也能测准电量1.1 库仑计的成本和槽点库仑计的原理是电流积分通过一个采样电阻串联在电池回路里实时采集电流再对时间积分算出“已经消耗了多少电量”。这个思路本身没问题精度也确实高但放到实际项目里槽点非常明显。首先是成本。一颗像样的电量计IC零售价从几块钱到十几块钱不等加上外围的采样电阻、滤波电容、PCB布局要求单这一块的物料成本就比一颗STM32G030还贵。对几十块钱成本的小产品来说这很不友好。其次是配置繁琐。电量计IC不是焊上去就能用它需要配置电池容量、放电曲线、截止电压等一堆参数还要在实验室里做充放电学习校准。遇到不同容量的电池参数还得重新调生产环节非常痛苦。再说电路上的坑。采样电阻的阻值选大了重负载下电阻压降太大影响系统供电选小了轻负载情况下信号太小根本测不准。想要在微安级待机和安培级峰值电流之间都测准至少得用双量程或高精度运放电路复杂度一下子拉满。所以我的结论是如果不是做几万套以上的产品、不是对电量精度有硬性要求用库仑计其实是“杀鸡用牛刀”。大多数场景只需要一个相对可靠的电量百分比这时候电压估算法反而是更合适的方案。1.2 换个思路用电压-电量曲线估算SOC锂电池和碱性电池不一样它的放电曲线相对平缓但也不是一条直线。开路电压与剩余电量之间存在一种接近“S型”的曲线关系。简单说就是满电4.2V时约100%放到3.6V左右时大概还剩四分之一左右等到3.3V以下基本就是空电状态了。既然这种对应关系存在那我只要把电池端电压测量准再通过查表把电压映射成SOC百分比就能实现不需要库仑计的电量监测。这个方法有个专业叫法开路电压法OCV法。不过要强调一点这里的电压指的是电池在“静置”状态下测得的开路电压。如果设备正在以大电流放电电池内阻会导致端电压明显低于开路电压这时候拿带载电压去查表SOC会偏低很多。比如电机堵转瞬间4.0V的电池可能被拉到3.7V查表结果就从85%掉到40%了。我在后面代码部分会专门讲怎么处理这个坑。1.3 内部基准VREFINT为什么能提高精度刚才说要把电压测准但STM32的ADC测量精度其实没有想象中那么高主要原因是ADC的参考电压不完美。大多数STM32的ADC参考电压就是VDDA也就是给MCU供电的3.3V电源。如果这个3.3V本身有误差比如实际只有3.28V那么ADC满量程就不是标称的3.3V测出来的数值自然也不准。还有个容易忽略的问题很多场合VDDA本身会波动。比如电池电压降到3.7V经过LDO给MCU供电LDO输出可能是3.29V等电池掉到3.4V时LDO输出可能变成3.25V。VDDA一变ADC的“尺子”就变了你读数4095代表的电压值也跟着变。STM32内部基准VREFINT就是用来解决这个问题的。它本质上是一个带隙基准电压源产生一个大约1.2V左右的、几乎不随供电电压和温度变化的稳定电压。这个基准被接到了ADC的一个内部通道上和普通外部通道一样可以被采样。也就是说每次我测电池电压之前先顺手读一下VREFINT通道的ADC值。如果VDDA准确等于3.3V那么VREFINT的ADC读数应该是一个“标准值”如果VDDA偏低ADC读数就会偏大因为同样的基准电压在更低的参考电压下对应的数字量更大。根据这个关系我就能反推出当前VDDA的实际值再用它去校准必须测的外部通道。这样一来VDDA不准的问题就被软件修掉了不需要加任何硬件只增加几个ADC寄存器配置和一条除法公式。1.4 这种方案的误差边界这个方案并不是万能的它的精度边界我要说清楚免得你项目做到一半发现不满足需求来骂我。电压法最大的软肋是动态负载。电池在大电流放电时内阻和极化效应会造成端电压明显下降而且这个下降量与电流、温度、电池老化程度都有关无法用简单公式精确补偿。所以如果产品长期处于脉冲放电或大电流工作状态SOC读数会忽高忽低。解决办法是配合负载检测只在轻载或静置时更新SOC重载时保持上一次SOC不变。另外就是温度。锂电池在低温下内阻增大同样电量下测到的电压会更低会让人误以为电量不足。做室外设备的话最好用NTC采集温度在查表时对不同温度曲线做补偿。如果只是室内使用温度影响可以忽略。理论上这套方案能做到常温静置态±5%以内的SOC误差电压测量误差能压到±1%左右。前提是分压电阻精度够、内部基准校准代码写对。2. 硬件设计两个电阻加一路ADC怎么设计最稳2.1 分压电阻选型与阻值计算锂电池最高电压是4.2V有些新型电芯甚至能到4.35V而STM32 ADC输入范围最高到VDDA通常就是3.3V。所以直接拿电池电压进ADC等于找死轻则读数饱和重则烧坏GPIO。加一个电阻分压电路是必须的。最简单的是两个电阻串联中间抽头进ADC。电池正极接R1到抽头抽头接R2到GND。分压比 R2 / (R1 R2)。选型时主要考虑两点第一采样精度。R1和R2的精度尽量选1%精度的贴片电阻因为分压比的偏差直接影响最终电压测量结果。两个1%电阻组合最坏情况下分压比误差可能接近2%这足以让SOC查表结果差出5个百分点。想要更高精度可以用0.1%的精密电阻或者用可调电阻做标定但一般情况下1%够用了。第二分压支路的静态电流。电池通过R1和R2串联到地的那一路会一直有一个微小电流在放电这个电流越大对电池续航的拖累越大。以R1R2100kΩ为例4.2V电池下静态电流 4.2 / (100k 100k) 21μA。对1000mAh电池来说理论上要5年才会放完看似无所谓但对几十毫安时的小电池或者要求超低待机功耗的设备来说还是不行。所以我建议把阻值选大一点比如R1R2100kΩ起步或者直接用1MΩ级别的分压。当然阻值不是越大越好ADC的采样电容在采样瞬间会从外部汲取一点电荷外部阻抗太高会导致采样值偏低。STM32数据手册里一般建议外部阻抗不超过几十kΩ到几百kΩ具体看采样时间和ADC配置。我实测下来100kΩ级别配239.5周期的采样时间稳定性和精度都还可以1MΩ就把采样时间拉满否则数值会偏低且跳动。如果你非要用大电阻可以考虑中间加一级运放跟随器但那样电路就复杂了。我常用的方案R1100kΩR2100kΩ分压比1/2满电4.2V时ADC抽头电压2.1V留了很大余量安全。如果电池可能出现4.35V2.175V也远低于3.3V依然安全。这个余量大的好处是万一LDO输出偏高一点点或者电池电压异常也不至于烧ADC。2.2 内部基准校准能消掉哪些误差消不掉哪些很多读者对“内部基准校准”有误解以为只要用了内部基准所有测量误差都消失了。实际上它只能消除一部分误差你得心里有数。能消除的VDDA绝对误差和漂移。不管VDDA实际是3.29V还是3.25V只要在采样瞬间同时采集VREFINT通道和被测通道两个ADC值相除参考电压项就会被约掉。这是内部基准校准最核心的价值。消不掉的分压电阻的精度误差。你在面包板上实际焊的电阻阻值和标称值之间可能差1%分压比就不是理论值。内部基准校准管不到这些所以这部分误差需要靠选精密电阻或者软件标定来处理。还要注意VREFINT本身也有芯片个体差异不同MCU之间的VREFINT电压可能在1.18V到1.24V之间波动。手册会给一个典型值比如1.20V拿典型值算所有芯片等于引入了一个固定的偏移。想彻底解决需要读芯片出厂校准值部分STM32系列在系统存储区保存了VREFINT校准参数可以在复位后读出来直接使用。更高端的做法是出厂标定时用精密电压源测一次把修正系数存到Flash里。作为参考我总结一下误差来源的占比VDDA不准通常造成2%左右的读数偏差分压电阻精度造成1%左右的偏差VREFINT个体差异造成1%左右的偏差。做好内部基准校准并选好电阻后总的电压测量误差大概在1%~2%水平已经远好于直接读ADC的3%~5%。2.3 低功耗场景下的开关分压方案如果设备需要休眠待机分压电路那20多微安的常驻电流也得省。做法很简单分压电阻的地端不直接接GND而是接一个NMOS的漏极NMOS源极接GND栅极由GPIO控制。平时GPIO输出低电平MOS管关断分压支路没电流要测电压时GPIO拉高MOS管导通分压支路工作采样完成后立刻再关断。这样待机时分压支路的漏电流就只剩MOS管本身的微小漏电流基本可以忽略。代价是采样瞬间会有一点点建立时间所以代码里在开启分压电源后最好等2~5ms让电压稳定再去读ADC。还有一个小细节如果MCU的ADC参考电压是VDDA而VDDA在休眠后可能被LDO关断这种方案就无效了。所以这个开关分压方案一般用在MCU始终供电、只是进入低功耗模式的场景。如果连MCU都断电那就得用外部定时器唤醒或者干脆让采样电路独立供电复杂度就上去了。3. 代码实现从ADC采样到SOC查表完整流程3.1 ADC初始化与内部基准读取代码我以STM32F103 HAL库为例其他型号思路完全一样哪怕是F0、G0系列也有同样的VREFINT通道。先说ADC初始化。我要用ADC1的两个通道外部电池电压通道假设接在PA1上对应ADC1_IN1和内部VREFINT通道F103上是ADC1_IN17。初始化代码里需要把这两个通道都配置到同一个ADC转换序列里这样连续读取的效率更高。static ADC_HandleTypeDef hadc1; static ADC_ChannelConfTypeDef sConfig {0}; void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_ENABLE; // 多通道扫描 hadc1.Init.ContinuousConvMode DISABLE; // 单次转换省电 hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; // 软件触发 hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 2; // 两个通道 HAL_ADC_Init(hadc1); // 通道1电池电压经过分压后进入 sConfig.Channel ADC_CHANNEL_1; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; // 采样时间长一点抗噪声 HAL_ADC_ConfigChannel(hadc1, sConfig); // 通道17内部基准电压VREFINT sConfig.Channel ADC_CHANNEL_17; sConfig.Rank 2; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); // 校准ADC这个校准是ADC自带的offset校准和VREFINT校准是两码事 HAL_ADCEx_Calibration_Start(hadc1); }有一点值得提醒ADCEx_Calibration_Start是ADC内部的零位校准必须在ADC启动前调用它对测量精度有好处但和内部基准校准不是同一件事。很多人把这两件事搞混以为做了这个就不用做VREFINT校准了实际上两个都要做。还要注意在HAL库初始化ADC之前如果是在STM32CubeMX里配置记得打开ADC1的Enable Regular Conversions并添加这两个通道内部基准不需要额外引脚配置直接在ADC配置里选ADC_CHANNEL_17就行。3.2 电压换算公式的推导与实现核心公式先放在这里然后我解释怎么来的V_BAT (VREFINT_TYP * ADC_VREFINT_MEAN 之类) 不好我直接写最终使用的代码和公式。设VREFINT_TYP内部基准典型电压F103大约1.20V一般取1.20或者1.21adc_refADC读取VREFINT通道得到的数值adc_chADC读取电池分压后通道得到的数值ratio (R1 R2) / R2分压倍率那么 V_SENSE VREFINT_TYP * adc_ch / adc_ref V_BAT V_SENSE * ratio为什么这个公式里没有用到4095因为ADC读数本身就是相对于参考电压的比例。adc_ref实际上等于 4095 * VREFINT_TYP / VDDAadc_ch等于 4095 * V_SENSE / VDDA两者相除之后VDDA和4095全部消掉只剩下真实电压的比例关系。这就是内部基准校准的精髓不需要知道VDDA具体是多少也不需要关心ADC是12位还是10位只要两个通道在同一个VDDA条件下完成采样误差就自动抵消。注意这两个采样必须在VDDA基本一致的条件下完成。如果中途VDDA迅速变化那相除之前和相除之后已经不对应了校准效果会变差。常规做法是让两个通道放在同一个转换序列里连续采样此时VDDA在微秒级时间内变化极小完全没问题。#define ADC_REF_VREFINT_TYP 1.20f // 内部基准典型值F103约1.20V #define DIVIDER_R1 100000.0f #define DIVIDER_R2 100000.0f #define DIVIDER_RATIO ((DIVIDER_R1 DIVIDER_R2) / DIVIDER_R2) uint16_t ADC_GetValue(uint32_t channel) { ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel channel; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t val (uint16_t)HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return val; } float GetBatteryVoltage(void) { uint16_t adc_ref ADC_GetValue(ADC_CHANNEL_17); uint16_t adc_ch ADC_GetValue(ADC_CHANNEL_1); float vsense ADC_REF_VREFINT_TYP * (float)adc_ch / (float)adc_ref; return vsense * DIVIDER_RATIO; }这里的ADC_GetValue每次重新配置通道好处是简单缺点是多了一些切换开销。实际上如果想让两个通道严格在同一个VDDA条件下采样应该一次性将两个通道配置进序列然后用DMA依次读取时间间隔更短。我为了代码易读性简化了实际项目里建议用多通道扫描加DMA。从转换结果来看两次HAL_ADC_ConfigChannel之间间隔很短VDDA漂移造成的误差微乎其微所以这样写问题也不大。3.3 电压-电量查表与线性插值拿到电压之后剩下的工作就是把这串电压数值映射成SOC百分比。我建议不要用一个简单的线性公式比如“(电压 - 3.0) / 1.2 * 100”这种虽然简单但误差太大。锂电池放电曲线明显分成三段4.2V到3.9V这一段电压掉得快3.9V到3.6V是平台期电压变化很慢3.6V以下又是快速跌落区。线性公式在平台期会让SOC跳动夸张你看一眼APP上电量从80%突然变成60%就是这么来的。正确做法是建一张电压-电量对照表用线性插值去补中间值。这张表的数据来源可以有两种一是直接用电池厂商提供的典型开路电压(SOC)表二是自己用实际电池做一次恒流放电记录数据。如果偷懒可以参考我这个典型三元锂电池静置态数据表开路电压(V)剩余电量SOC(%)4.201004.10904.00803.90683.80543.70403.60263.50153.4083.3043.000注意这是单节三元锂电池的数据磷酸铁锂的曲线完全不是这样它的平台期更平缓3.3V还能有70%的电量所以千万别拿这个表去套所有电池。插值代码很简单先判断电压是否超出表的上限或下限然后遍历数据表找到电压落在哪两个点之间按比例算SOCtypedef struct { float voltage; float soc; } SOCPoint; static const SOCPoint soc_table[] { {4.20f, 100.0f}, {4.10f, 90.0f}, {4.00f, 80.0f}, {3.90f, 68.0f}, {3.80f, 54.0f}, {3.70f, 40.0f}, {3.60f, 26.0f}, {3.50f, 15.0f}, {3.40f, 8.0f}, {3.30f, 4.0f}, {3.00f, 0.0f}, }; #define TABLE_LEN (sizeof(soc_table) / sizeof(soc_table[0])) float VoltageToSOC(float volt) { if (volt soc_table[0].voltage) return soc_table[0].soc; if (volt soc_table[TABLE_LEN - 1].voltage) return 0.0f; for (int i 0; i TABLE_LEN - 1; i) { if (volt soc_table[i].voltage volt soc_table[i 1].voltage) { float ratio (soc_table[i].voltage - volt) / (soc_table[i].voltage - soc_table[i 1].voltage); return soc_table[i].soc ratio * (soc_table[i 1].soc - soc_table[i].soc); } } return 0.0f; }这个查表方法看起来简单但实测比很多复杂的拟合公式都好使因为电池放电曲线本身就是实验数据实验数据插值天然最准确。如果你有更精细的需求可以把数据表扩大到40个点每个0.02V一个点插值会非常平滑。3.4 滤波、低功耗唤醒与状态判断直接拿单次ADC转换结果去查SOC显示值一定会跳得你怀疑人生。ADC本身有量化噪声和热噪声分压电路上还会耦合一点电源纹波单次采样的波动常常能有0.01V到0.03V。这个电压波动映射到SOC上可能就带来3~5个百分点的跳变。所以必须做滤波。最简单的办法是均值滤波连续读十次去掉一个最大值一个最小值之后再求平均。我习惯用一个长度为8或16的环形缓冲区每200ms采一个点滑动计算平均值。采样间隔太短没用因为噪声不是白噪声连续采样之间关联性太强滤波效果差间隔拉长一点比如100ms以上才能有效滤除周期性干扰。下面是一个滑动均值的参考实现#define SOC_SAMPLE_COUNT 8 static float soc_sample_buf[SOC_SAMPLE_COUNT]; static uint8_t soc_sample_idx 0; float BatterySOC_Filtered(float adc_volt) { soc_sample_buf[soc_sample_idx] adc_volt; soc_sample_idx (soc_sample_idx 1) % SOC_SAMPLE_COUNT; float sum 0.0f; float maxv 0.0f, minv 100.0f; for (int i 0; i SOC_SAMPLE_COUNT; i) { sum soc_sample_buf[i]; if (soc_sample_buf[i] maxv) maxv soc_sample_buf[i]; if (soc_sample_buf[i] minv) minv soc_sample_buf[i]; } sum - maxv; sum - minv; return VoltageToSOC(sum / (SOC_SAMPLE_COUNT - 2)); }另外前面提到过带载电压会让SOC偏低我建议加一个滞回逻辑当这次计算的SOC和上一次的差异超过5%时不要立刻跳变而是朝新值方向每次只移动1%~2%。这样即使电机启动瞬间电压掉了0.3V显示的电量也只是慢慢往下溜一点不会瞬间从80%跳到40%。这种软件滞回的处理比一味滤波更自然。还有一个会踩的坑低功耗模式下如果让ADC继续按固定周期唤醒采样那低功耗就白做了。比较好的做法是仅在开机、收到按键、或者定时唤醒的瞬间执行一次采样采完立刻把ADC时钟关掉同时把分压MOS管关断。一次完整采样流程在几十毫秒内完成对整体功耗的影响可以忽略。4. 实测效果、常见问题与调试技巧4.1 实测数据与误差分析我拿手头一块STM32F103C8T6最小系统板加一个18650锂电池做了测试。分压电阻用的就是两个100kΩ、1%精度贴片电池电压用四位半万用表实测作为基准MCU的测量结果通过串口打出来对比数据如下万用表实测(V)MCU读取(V)偏差(mV)MCU查表SOC(%)4.194.18-10994.034.02-10823.883.87-10673.723.71-10423.553.54-1020这个10mV的偏差基本可以归结为分压电阻阻值误差因为两个100kΩ 1%电阻即使在最理想情况下也有0.5%的分压比偏差4V附近就是20mV量级。如果想把这最后的10mV也消掉可以做一点软件修正把V_BAT乘以一个修正系数比如这个板子上修正系数约为1.006。这个系数在出厂时用精密电压源标定一次就能确定。测试中还发现如果ADC采样时间设置成1.5周期的极短采样读数跳动能达到±20mV把采样时间调到239.5周期后跳动降到±3mV以内。原因是STM32 ADC的采样电容需要足够时间充电到输入电压外部电路阻抗100kΩ时短采样时间充电不充分读数明显偏低且抖动。这个坑特别容易踩强烈建议采样时间往大了配。4.2 常见问题速查表我整理了一些读者和客户经常遇到的问题方便你对照排查。现象根本原因解决办法测出来的电压比万用表低0.1V以上分压电阻阻值偏大或采样时间太短导致ADC采样电容没充满换1%高精度电阻采样时间调到239.5周期测出来的电压比万用表高分压电阻的GND端虚焊或分压比计算错误用万用表实测R1和R2阻值代入公式重新算ratioSOC显示跳动忽高忽低没做滤波或带大负载时直接查表用均值滤波加5%滞回逻辑重负载时不更新SOC不同设备之间显示电量差异很大VREFINT个体差异没有被校准读取芯片出厂校准值替换VREFINT_TYP典型值待机时电池电量下降很快分压电阻常驻导通一直有20μA左右的漏电流改成GPIO控制MOS管开关分压支路只在采样时导通电池充电时只能充到4.0V就显示满电充电管理芯片判满条件或浮充电压设置问题不是测量问题查充电IC的截止电流和充电电压确认CCC模式转换点这里特别说一下“4.2V锂电池只能充到4.0V就满”这个问题很多人会把锅甩给电量监测电路其实是两个层面的事。充电管理芯片通过检测充电电流来判断是否充满如果恒压阶段的截止电流设得太高电池还没真正到4.2V就被判定充满。另外大内阻的劣质电池在恒压阶段电流衰减很快也可能导致提前判满。这和本项目的电压测量无关但经常一起出现所以列出来提醒你排查方向别搞错。还有三元锂电池4.12V对应多少电量的疑问按我前面那个典型放电曲线查表4.12V约对应93%左右。这不是一个固定值和放电倍率、电池温度都有关但你可以拿这个数据当参考。4.3 最后分享几个调试小技巧如果你在调试时不确定代码里的VREFINT通道是否正确可以在代码里把adc_ref的值打印出来观察正常情况下adc_ref应该在1100到1400之间对应VDDA在3.0V到3.6V范围。如果adc_ref读到4095或者0大概率是通道配置错了或者ADC时钟没开。判断分压电路是否正常可以直接把分压抽头电压打印出来满电电池时分压抽头应该在2.1V左右对应的adc_ch在2400附近。如果adc_ch明显偏高可能是两个分压电阻焊反了或者GND没接好。我曾经在测试时遇到过一个很隐蔽的问题电池电压测量值随着时间的推移往上涨了0.05V。查了半天发现是ADC每次读取后在停止状态下仍然给外部电路提供一个微小的上拉电流把分压抽头电容慢慢充上去了。解决方法是每次采样完成后将ADC输入通道切换到内部通道或者干脆把分压MOS管关断。这属于很边缘的问题但如果你发现读数随时间漂移可以往这个方向查。关于采样时机我习惯在设备每次进入空闲状态时主动触发一次电压采样。比如按键按下后点亮屏幕的那一瞬间MCU先花50ms把电压测完然后再点亮屏幕做后续操作。屏幕点亮后背光电流会导致电池端电压轻微下降如果测量时机放在点亮之后读到的电压就会偏低一点电量的显示偏差能差出2~3个百分点。这个顺序调一下效果立竿见影。以上这套方案的核心就是把“测电压”这个本来就有成本的事做到足够好然后用查表避开库仑计的复杂度。实际项目里你甚至可以把代码里的VREFINT_TYP和soc_table都做成可配置项放到Flash的开头专门留一个标定区域生产时逐台写入这样同一套固件就能适配不同批次的电池。对我这种常年搞小批量硬件的人来说这种低成本、可以自己掌控的电池电量方案比堆库仑计IC要顺手得多。
返回列表