news 2026/8/29 4:12:32

STM32 ADC从入门到精通:配置、DMA采集与滤波实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 ADC从入门到精通:配置、DMA采集与滤波实战

1. 项目概述:从模拟世界到数字世界的桥梁

做嵌入式开发,尤其是涉及到传感器数据采集、电池电压监控或者音频信号处理时,我们总会遇到一个核心问题:如何让只认识0和1的微控制器,去理解和处理现实世界中连续变化的电压信号?这就是ADC(模数转换器)要干的活儿。STM32作为业界最流行的ARM Cortex-M内核微控制器家族,其内置的ADC外设功能强大且灵活,但相应的配置选项也多,新手很容易在采样率、分辨率、触发方式这些参数上绕晕。

这个实验的核心,就是带你亲手打通STM32的ADC功能,把外部一个可变的模拟电压(比如用电位器分压得到的)转换成单片机内部可以处理的数字值,并通过串口打印出来观察。这不仅是学习STM32的必修课,更是后续所有涉及数据采集、智能控制项目的基石。无论你是刚接触STM32的学生,还是需要快速回顾ADC要点进行产品开发的工程师,通过这个从原理到代码、从配置到调试的完整流程,你都能建立起清晰、实用的认知,避开那些我早年踩过的坑。

2. ADC核心原理与STM32的ADC外设解析

2.1 ADC是如何工作的:从连续到离散的魔法

要玩转ADC,不能只停留在调用库函数的层面,得先明白它底层是怎么“变魔术”的。模拟信号在时间上和幅度上都是连续的,而数字信号是离散的。ADC的转换过程,可以通俗地理解为“量化”和“编码”两步。

想象一下你要用一把刻度尺去测量一段连续变化的长度。首先,你需要确定尺子的最小刻度(比如1毫米),这就是分辨率,对应ADC的位数。一个12位的ADC,意味着它能把参考电压范围分成 2^12 = 4096 个离散的等级。如果参考电压是3.3V,那么每个数字等级(1 LSB)代表的电压就是 3.3V / 4096 ≈ 0.8mV。你测得的长度值,最终会以最接近的那个刻度值来报告,这个过程就是量化。量化必然带来误差,这个误差就是量化误差,理论上最大为 ±0.5 LSB。

STM32的ADC主要采用逐次逼近型(SAR)架构。它的工作原理很像天平称重:先用一半的参考电压(相当于最大的砝码)与输入电压比较,如果输入电压更大,则保留这个“砝码”(对应数字位输出1),并加上一半的剩余电压(下一个更小的砝码)继续比较;如果更小,则丢弃这个“砝码”(输出0),用剩余电压的一半继续比较。如此反复,从最高位(MSB)到最低位(LSB)逐位确定,最终得到一串二进制数字,这就是编码

注意:量化误差是原理性误差,无法消除。高分辨率ADC可以减少这个误差,但成本、转换时间也会增加。对于STM32常见的12位ADC,在要求不高的场合完全够用,但在需要高精度测量(如精密仪表)时,需要额外注意基准电压源、PCB布局和软件滤波。

2.2 STM32 ADC的关键特性与模式选择

不同系列的STM32(如F1、F4、H7)其ADC外设能力有差异,但核心概念相通。这里以最常见的STM32F1/F4系列为例,解析几个你必须掌握的关键特性。

1. 分辨率与转换时间:分辨率有6位、8位、10位、12位可选,通常我们用12位以获得最佳精度。转换时间是一个关键参数,它决定了ADC能跑多快。转换时间 = 采样时间 + 逐次逼近时间(固定为12.5个ADC时钟周期)。采样时间可以软件配置,单位是ADC时钟周期,时间越长,采样保持电容充电越充分,对抗输入信号源阻抗的能力越强,结果越准确,但整体转换速率就越慢。你需要根据信号频率和精度要求来权衡。

2. 扫描模式与连续模式:这是配置时最容易混淆的两个概念。

  • 扫描模式(Scan Mode):针对多通道采集。使能后,ADC会按照规则序列寄存器(SQR)里设定的通道顺序,一个接一个地自动转换。单次扫描完成后停止。
  • 连续模式(Continuous Mode):针对单通道或多通道的循环采集。使能后,ADC在完成一次(或一轮扫描)转换后,立即自动开始下一次转换,周而复始,除非你手动停止它。

通常,如果是多通道采集,我们会使能扫描模式。至于是否使能连续模式,则取决于你需要单次触发还是持续不断地获取数据。

3. 触发源:ADC转换可以由软件触发(调用HAL_ADC_Start),也可以由硬件事件触发,这才是ADC发挥威力的地方。硬件触发源包括:

  • 定时器触发:这是最常用的方式。可以用一个定时器的更新事件(UEV)、比较匹配事件(CHx)等来精确地、周期性地触发ADC采样。例如,做音频采样时,就需要一个精确的定时器来产生固定的采样率(如44.1kHz)。
  • 外部引脚触发:通过特定的GPIO引脚上升沿/下降沿来触发,适合响应外部事件。

4. 数据对齐与DMA:12位的转换结果默认是右对齐的,存放在一个16位的数据寄存器里。你也可以选择左对齐,方便后续进行某些计算。当进行高速、多通道连续采样时,CPU频繁进入中断去读取数据会成为瓶颈,并且可能丢失数据。此时必须启用DMA(直接存储器访问)。DMA可以在ADC转换完成后,自动将数据搬运到你指定的内存数组(Buffer)中,搬运完成后才通知CPU,极大减轻CPU负担,实现高效稳定的数据流。

3. 实验环境搭建与CubeMX基础配置

3.1 硬件连接与电路设计要点

我们以一个最简单的实验为例:使用一个10kΩ的多圈电位器,将其两端分别接到3.3V和GND,中间抽头(滑动端)接到STM32的某个ADC输入通道引脚(例如PA0)。这样,旋转电位器,PA0上的电压就在0~3.3V之间线性变化。

实操心得:别小看这个简单的电路。首先,确保你的3.3V电源是干净的,噪声要小。如果直接从板载的LDO取电,要留意当数字部分功耗大时,3.3V上可能会有毛刺,影响ADC读数稳定性。对于精度要求高的场合,建议使用独立的基准电压芯片(如REF3033)为ADC的参考电压引脚(VREF+)供电。其次,如果信号源阻抗较高(比如用长导线连接),可以在ADC输入引脚对地加一个几十到几百皮法的小电容,起到滤波和提供瞬时电荷的作用,但电容太大会影响信号变化速度。

硬件清单:

  • STM32最小系统板(如STM32F103C8T6蓝色小板)
  • ST-Link V2调试下载器
  • 10kΩ电位器一个
  • 杜邦线若干
  • 电脑(安装Keil MDK或STM32CubeIDE)

3.2 使用STM32CubeMX进行图形化配置

STM32CubeMX+HAL库极大地简化了配置过程,但理解其生成的代码至关重要。

  1. 创建项目与选择ADC引脚:打开CubeMX,选择你的MCU型号。在Pinout视图中,找到你想用的ADC通道,比如PA0,将其功能设置为ADC1_IN0。CubeMX会自动将其配置为模拟输入模式,这是正确的,ADC引脚必须设为模拟输入以避免额外的电流消耗和干扰。

  2. 配置ADC参数:在左侧“Analog”分类下找到ADC1

    • Mode:选择“Independent mode”(独立模式)。对于双ADC的复杂模式(如交替、同步),初期先不用。
    • Parameter Settings标签页:
      • Resolution:选择“12-bit”(12位分辨率)。
      • Scan Conversion Mode:因为我们只用一个通道,选择“Disabled”。如果是多通道,则需Enable。
      • Continuous Conversion Mode:选择“Disabled”。我们采用软件单次触发。
      • Discontinuous Conversion Mode:禁用。
      • DMA Continuous Requests:禁用(单次转换不需要DMA)。
      • End Of Conversion Selection:选择“EOC after each conversion”(每次转换后产生EOC事件)。
    • Analog WatchDog:模拟看门狗,可以先禁用。它的作用是当转换结果高于或低于你设定的阈值时触发中断,常用于电池电压监控。
    • Sampling Time:在“Channel Configuration”子标签下,选择我们配置的Rank1(规则序列中的第一个),设置其Sampling Time。对于电位器这种慢速信号,选择“239.5 Cycles”可以获得很好的噪声抑制效果。对于高速信号,则需要减少这个时间。
  3. 配置时钟树:ADC的时钟(ADCCLK)由APB2时钟分频而来。STM32F1的ADC时钟最高14MHz,F4系列更高。确保你的ADCCLK不超过芯片手册规定的最大值。在Clock Configuration页面,合理配置系统时钟和APB2分频,使ADCCLK在安全范围内。

  4. 生成代码:在Project Manager页面设置好工程名、路径、IDE(Keil或CubeIDE),然后点击“Generate Code”。CubeMX会生成完整的初始化代码。

4. 软件编程:从单次采样到DMA连续采集

4.1 基础单次采样与软件触发

生成了代码后,我们打开工程,在main.c的用户代码区编写采集逻辑。

// 在main函数初始化部分之后,进入主循环之前,可以开始一次转换 HAL_ADC_Start(&hadc1); // 启动ADC转换(软件触发) // 等待转换完成,超时时间设为10ms if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { // 读取转换结果,12位右对齐,所以值范围是0-4095 uint16_t adc_value = HAL_ADC_GetValue(&hadc1); // 将数字值转换为电压值 (假设Vref+ = 3.3V) float voltage = (float)adc_value / 4095.0f * 3.3f; // 通过串口打印出去 printf("ADC Value: %d, Voltage: %.3f V\r\n", adc_value, voltage); } HAL_ADC_Stop(&hadc1); // 停止ADC

这段代码放在while(1)循环里,就能不断采样并打印。但HAL_ADC_PollForConversion阻塞式的,会占用CPU时间等待。在实际产品中,更推荐使用中断方式DMA方式

4.2 中断方式采集

中断方式避免了CPU空等,转换完成后自动调用回调函数。

  1. 在CubeMX中使能ADC全局中断:在ADC配置的NVIC Settings标签页,勾选ADC1 and ADC2 global interrupts
  2. 编写代码
// 启动ADC中断模式转换 HAL_ADC_Start_IT(&hadc1); // 转换完成中断回调函数(需重写) void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADC1) { uint16_t adc_value = HAL_ADC_GetValue(&hadc1); // 处理数据,例如存入缓冲区 // 注意:在中断回调函数中做耗时操作(如printf)是坏习惯! // 通常只设置标志位或拷贝数据。 g_adc_value = adc_value; g_conversion_done_flag = 1; } } // 在主循环中检查标志位 while (1) { if (g_conversion_done_flag) { g_conversion_done_flag = 0; float voltage = (float)g_adc_value / 4095.0f * 3.3f; printf("Voltage: %.3f V\r\n", voltage); // 如果需要连续采样,再次启动中断转换 HAL_ADC_Start_IT(&hadc1); } // ... 其他任务 }

4.3 DMA多通道连续采集(实战精华)

这是工业级应用中最常用的模式。假设我们需要以10kHz的速率,循环采集通道0(PA0)和通道1(PA1)的电压。

  1. CubeMX配置

    • 在ADC1的Parameter Settings中,使能Scan Conversion ModeContinuous Conversion Mode
    • DMA Settings标签页,点击Add,选择ADC1,模式选择Circular(循环模式),数据宽度都选择Word(半字,对应16位)。这样DMA会周而复始地将数据搬运到指定数组。
    • Rank配置中,添加两个Rank,分别选择通道0和通道1,设置相同的采样时间。
    • 配置一个定时器(如TIM2)来触发ADC。在ADC1的Trigger Configuration中,选择Trigger SourceTimer 2 Trigger Out event。然后去配置TIM2,使其产生一个10kHz的更新事件(例如,系统时钟72MHz,分频后计数到7200-1更新一次,即可得到10kHz频率)。
  2. 软件编写

// 定义缓冲区,DMA会循环填充它 // 我们定义两个通道,每个通道的数据是uint16_t,缓冲区长度设为每个通道10个样本 #define ADC_BUFFER_SIZE 20 // 2通道 * 10样本 uint16_t adc_buffer[ADC_BUFFER_SIZE] = {0}; // 在main函数初始化后,启动DMA HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE); // 然后启动定时器,ADC便会以10kHz的速率被定时器自动触发,DMA自动搬运数据。 // 主循环中,我们可以直接访问adc_buffer来获取最新数据 // 缓冲区排列顺序取决于Rank顺序,假设Rank1是CH0,Rank2是CH1 // 那么 buffer[0] = CH0样本1, buffer[1] = CH1样本1, buffer[2] = CH0样本2, buffer[3] = CH1样本2... while (1) { // 计算最近一次CH0和CH1的转换结果平均值 uint32_t sum_ch0 = 0, sum_ch1 = 0; for (int i = 0; i < 10; i++) // 取10个样本平均 { sum_ch0 += adc_buffer[i * 2]; // 偶数索引是CH0 sum_ch1 += adc_buffer[i * 2 + 1]; // 奇数索引是CH1 } float voltage_ch0 = (float)(sum_ch0 / 10) / 4095.0f * 3.3f; float voltage_ch1 = (float)(sum_ch1 / 10) / 4095.0f * 3.3f; // 打印或处理... HAL_Delay(100); // 每100ms处理一次 }

核心技巧:使用Circular模式的DMA时,缓冲区就像一个“滑动的窗口”,CPU可以随时从里面读取最新的数据,而无需担心数据覆盖冲突,因为DMA的写指针和CPU的读指针是独立运行的。这是实现高速实时数据流处理的关键。

5. 数据滤波与校准:让读数更精准可靠

直接从ADC读出的值往往是“毛躁”的,跳动几个LSB是常有的事。硬件上的噪声无法完全消除,但我们可以通过软件滤波来获得更稳定、更可信的结果。

5.1 常用软件滤波算法

  1. 算术平均滤波:最简单有效。连续采样N次取平均值。N越大,平滑效果越好,但响应速度越慢。适用于一般性信号。

    #define N 10 uint32_t filter_average(uint16_t new_sample) { static uint16_t buf[N] = {0}; static uint8_t index = 0; static uint32_t sum = 0; sum -= buf[index]; // 减去最旧的值 buf[index] = new_sample; // 存入新值 sum += new_sample; // 加上新值 index = (index + 1) % N; // 更新索引 return sum / N; // 返回平均值 }
  2. 滑动平均滤波:与算术平均类似,但通常用一个固定长度的队列(FIFO)来实现,每次加入新值并剔除旧值,计算当前队列内所有值的平均。上述代码其实就是滑动平均的一种实现。

  3. 中值滤波:对采样窗口内的数据排序,取中间值。对脉冲性干扰(偶尔的尖峰毛刺)有奇效。比如采样5次,排序后取第三大的值作为输出。

    uint16_t filter_median(uint16_t new_sample) { static uint16_t buf[5] = {0}; static uint8_t idx = 0; uint16_t temp_buf[5]; buf[idx++] = new_sample; if (idx >= 5) idx = 0; // 拷贝到临时数组排序 memcpy(temp_buf, buf, sizeof(buf)); bubble_sort(temp_buf, 5); // 需要一个简单的排序函数 return temp_buf[2]; // 返回中值 }
  4. 一阶滞后滤波(低通滤波)Y(n) = α * X(n) + (1-α) * Y(n-1)。其中α是滤波系数(0<α<1),X(n)是新采样值,Y(n-1)是上次滤波输出。α越小,滤波越平滑,但滞后越严重。计算量小,适合MCU。

    #define ALPHA 0.2f // 滤波系数 float filter_lowpass(float new_sample) { static float output = 0; output = ALPHA * new_sample + (1 - ALPHA) * output; return output; }

经验之谈:没有一种滤波算法是万能的。对于缓慢变化的信号(如温度、电池电压),滑动平均或一阶滞后滤波效果很好。对于存在突发尖峰的信号(如按键检测,可能伴有抖动),中值滤波更适合。在实际项目中,我常常会组合使用,例如先进行中值滤波去除野值,再进行滑动平均进一步平滑。

5.2 ADC的校准与参考电压

要让ADC读数准确,除了滤波,校准也至关重要。

  1. 内部参考电压:STM32芯片内部有一个参照电压VREFINT,连接到ADC的一个特殊通道(如STM32F103是通道17)。这个电压在出厂时经过校准,典型值约1.2V,但每个芯片有细微差异,其精确值存放在芯片的系统存储区(Flash特定地址)。我们可以通过读取这个内部参考电压通道的值,来反推当前实际的VDDA(模拟供电电压)是多少,从而对其他通道的测量值进行比例校准,消除因VDDA波动带来的误差。这是提高精度的低成本方法。

  2. 偏移校准:HAL库提供了HAL_ADCEx_Calibration_Start()函数,用于执行ADC的偏移校准。它应该在ADC初始化之后、开始转换之前调用。校准过程会测量内部电路的误差并存储校准值,后续转换会自动补偿。特别注意:校准前,必须确保ADC已经上电并稳定一段时间,并且不要有任何外部信号输入到被校准的ADC通道。

6. 典型问题排查与调试技巧实录

即使配置看起来正确,ADC也可能出现各种“诡异”现象。下面是我在多年调试中总结的一些常见问题及解决方法。

6.1 问题一:ADC读数不稳定,跳动大

  • 现象:输入一个固定电压,读出的数字值在±10个LSB甚至更大范围内跳动。
  • 排查思路
    1. 检查电源和地:这是首要怀疑对象。用示波器查看模拟电源(VDDA)和地(VSSA)的波形,看是否有明显的噪声或纹波。确保VDDA通过磁珠或电感与数字电源VDD隔离,并接有足够的去耦电容(如10uF钽电容+100nF陶瓷电容靠近MCU引脚)。
    2. 检查信号源:信号源本身是否稳定?电位器接触不良也会导致跳动。可以尝试用稳压源直接给一个固定电压测试。
    3. 调整采样时间:增加采样时间,给采样保持电容更充分的充电时间,特别是当信号源阻抗较高时。这是软件上最直接的改善手段。
    4. 检查PCB布局:模拟信号走线要远离数字信号线、时钟线等高速噪声源。最好在模拟部分周围铺上完整的模拟地平面。
    5. 软件滤波:如上节所述,施加合适的软件滤波。

6.2 问题二:ADC读数永远为0或4095(满量程)

  • 现象:无论输入电压如何变化,读出的值始终是0x0000或0x0FFF。
  • 排查思路
    1. 引脚配置错误:确认GPIO是否已正确配置为模拟输入模式。如果配置成了推挽输出等模式,ADC无法正常工作。
    2. 参考电压问题:检查VREF+VREF-(通常VREF-接VSSA)的连接。如果VREF+悬空或接地,可能导致此现象。对于没有独立VREF引脚的型号,VDDA即作为参考正端。
    3. ADC未正确启动或触发:检查HAL_ADC_StartHAL_ADC_Start_ITHAL_ADC_Start_DMA是否被成功调用。如果是硬件触发,检查触发源(如定时器)是否已使能并正常工作。
    4. DMA溢出:在DMA模式下,如果DMA缓冲区设置太小,或者CPU处理速度跟不上DMA填充速度,可能导致数据被覆盖,读出异常值。检查DMA缓冲区大小和CPU处理逻辑。

6.3 问题三:多通道采样,通道间相互串扰

  • 现象:当只有一个通道有输入信号时,其他无信号的通道读数也会跟着变化。
  • 排查思路
    1. 采样时间不足:这是最主要的原因。在切换ADC多路复用器的通道时,需要足够的时间让内部电容放电并建立新通道的电压。必须增加采样时间,尤其是在高阻抗信号源的情况下。可以尝试将采样时间设置为所有可用选项中的最大值。
    2. 通道切换顺序:如果可能,在扫描序列中,将高阻抗通道安排在低阻抗通道之后,并确保有足够的时间间隔(通过采样时间控制)。

6.4 调试利器:利用串口与逻辑分析仪

  • 串口打印:最基础的调试方法。将原始ADC值、计算后的电压、滤波后的值实时打印出来,可以直观看到数据变化趋势和问题。注意打印频率不要太高,以免影响程序实时性。
  • 逻辑分析仪/示波器:高级调试必备。可以抓取ADC的触发信号(如定时器输出)、转换完成信号(EOC)以及DMA请求信号,精确分析时序是否满足要求,转换间隔是否恒定,有没有丢失触发脉冲等。这对于调试基于定时器触发的DMA连续采集模式至关重要。

ADC的稳定性是嵌入式系统感知外界的基础,它往往需要软硬件协同优化。从原理理解到配置实践,再到问题排查,每一步都藏着细节。我个人的体会是,把ADC玩透,你的嵌入式项目就成功了一半。下次当你需要采集温度、压力、光照或者音频时,你会感谢现在耐心调试ADC的自己。最后一个小技巧:在最终产品中,如果ADC精度要求极高,不妨在代码初始化后,加入一个短暂的延时(比如100ms),让模拟电源和ADC内部电路完全稳定,再进行校准和采样,往往会有意想不到的改善效果。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 4:10:42

基于ROS2与MoveIt 2的UR5机械臂抓取仿真环境搭建与应用实践

简介&#xff1a;机器人操作系统&#xff08;ROS&#xff09;作为机器人开发的事实标准&#xff0c;其第二代ROS2通过DDS通信中间件&#xff0c;在实时性、分布式和安全性上实现了显著提升&#xff0c;为复杂机器人应用提供了更可靠的软件框架。运动规划是机器人自主执行任务的…

作者头像 李华
网站建设 2026/8/29 4:10:11

8款专业AI写论文工具横向实测,本硕博撰稿避坑实操指南

前言&#xff1a;AI 写论文乱象频发&#xff0c;实测 8 款工具理清适配边界 每到毕业季&#xff0c;本科生、硕博生都会集中寻找 AI 论文辅助工具&#xff0c;市面各类写作软件层出不穷。然而&#xff0c;不少工具存在明显短板&#xff1a;虚假参考文献、无法匹配本校格式、不支…

作者头像 李华
网站建设 2026/8/29 4:09:39

单片机计算机毕设之基于 STM32 的压力与超声传感器健康测量终端开发 基于 STM32 的便携式身高体重 BMI 语音播报仪器设计(013705)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/29 4:09:24

单片机计算机毕设之基于 STM32 的水压温度传感器数据采集系统设计 基于 STM32 的 JDY-3X 蓝牙通信监测终端与 APP 设计(015405)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/29 4:08:41

【单片机课程设计/毕业设计】嵌入式环境测距感知、声光预警与蓝牙数据可视化系统 STM32 超声波测距、温度补偿及移动终端远程监控设计(014205)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/29 4:08:19

M-Blocks模块化自重构机器人:飞轮磁铁如何实现形态变换

我最初关注到 M-Blocks&#xff0c;是在一段很老的研究演示视频里&#xff1a;一堆巴掌大的立方体&#xff0c;像被施了魔法一样&#xff0c;自己翻滚过同伴的身体&#xff0c;自动吸附成新的形状&#xff0c;甚至还能从地面上轻轻跳起来&#xff0c;重新组合。当时弹幕里最多的…

作者头像 李华