news 2026/10/16 10:04:19

STM32F103ZET6裸机示波器程序:ADC+DMA+定时器触发与TFT屏幕波形显示实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103ZET6裸机示波器程序:ADC+DMA+定时器触发与TFT屏幕波形显示实战

简介:这是一份面向嵌入式初学者与电子爱好者的STM32F103ZET6简易示波器程序源码包,基于Cortex-M3内核芯片实现正弦波、方波等信号的采集与波形显示,适合用于学习ADC采样、LCD驱动与定时器配置等典型外设开发。压缩包共272个文件,约8.9MB,以c与h源码文件为核心,配合o、d、crf等编译中间文件,以及lst、txt、s等辅助说明与启动文件,另含uvproj、uvopt工程文件、hex烧录文件与map映射文件,可直接在Keil环境下打开编译。资源已有4570人学习下载,热度较高。项目完整呈现了从ADC模拟信号采集、数据处理到SPI/I²C接口驱动LCD显示波形的实现链路,并附带配置文件与说明文档,便于读者理解采样率控制、屏幕刷新与固件组织方式,是嵌入式系统入门与信号可视化实践的实用参考。

1. 一块 F103 板子跑出波形:这个示波器程序到底能干什么

如果你手头正好有一块 STM32F103ZET6 的开发板,屏幕、按键、ADC 输入这些外设都焊好了,却一直只跑过点灯和串口打印,那这份示波器程序值得你花一个下午拆开看。它做的事情很直接:把模拟信号从 ADC 引脚采进来,在本地屏幕上实时画出波形,同时把采样率、触发方式、电压档位这些参数做成可调项。整套逻辑跑在裸机环境里,不依赖操作系统,编译烧录后就能脱离电脑独立工作。

它适合两类人。一类是想找一个完整、能跑、代码量可控的 STM32 综合练手项目的开发者,因为示波器这个场景天然把 ADC、DMA、定时器、屏幕驱动、按键交互全串起来了,比单个外设 Demo 有嚼头。另一类是手上已经有 F103ZET6 板子、想把它改造成简易测量工具的人,比如测个传感器输出、看个 PWM 占空比、抓一段缓慢变化的电压曲线。需要说清楚的是,它是“简易示波器”,不是替代台式示波器的方案,带宽和采样率受限于 F103 的 ADC 和屏幕刷新能力,定位在低频信号观察和教学验证。

这份资源是源码包形式,拿到手就是一套可以编译的工程。下面我按“先看清结构、再跑起来、最后动手改”的顺序,把它拆一遍。

2. 工程结构与技术选型:从启动文件到屏幕刷新链路

2.1 目录骨架与各文件夹职责

拿到一个 STM32 工程,我第一件事不是打开 main.c,而是先看目录怎么分的。这套程序的结构是典型的裸机工程布局,大致长这样:

Project/ ├── CORE/ # 内核相关,启动文件与内核头文件 ├── STM32F10x_FWLib/ # 标准外设库源码 ├── HARDWARE/ # 板级驱动,屏幕、ADC、按键等 │ ├── ADC/ │ ├── LCD/ │ ├── KEY/ │ └── TIMER/ ├── USER/ # 主逻辑 │ ├── main.c │ ├── stm32f10x_it.c # 中断服务函数 │ └── system_stm32f10x.c └── OBJ/ # 编译输出

CORE和STM32F10x_FWLib基本不用动,属于地基。真正要读的是HARDWARE和USER两块:前者是外设驱动,后者是示波器的主循环和中断逻辑。这种分法的好处是改屏幕型号或者换 ADC 通道时,只动HARDWARE里对应文件夹,主逻辑不受影响。

提示:不同来源的工程目录名可能略有差异,但“库文件 / 硬件驱动 / 用户逻辑”这三层划分基本一致,认准这个结构就不会迷路。

2.2 技术选型:为什么是标准库而不是 HAL

这套程序用的是 STM32 标准外设库(Standard Peripheral Library),不是现在更流行的 HAL 库。原因很实际:F103 这一代芯片用标准库的代码更贴近寄存器,执行路径短,中断响应和 DMA 配置写起来直观,对于示波器这种对时序敏感的场景,少一层抽象就少一分不确定性。HAL 库的优点是跨系列移植方便,但代价是代码体积和调用层级都上去了,在 F103 这种资源有限的片子上,标准库反而更顺手。

核心外设的职责分工是这样的:

外设作用关键点
ADC1采集模拟信号配合 DMA 搬运,避免 CPU 轮询丢点
DMA把 ADC 结果搬到内存缓冲区循环模式,双缓冲或半满中断
TIM触发 ADC 采样,控制采样率定时器触发比软件触发稳定
FSMC驱动 TFT 屏幕并口屏幕用 FSMC 刷得快
EXTI按键输入用于切换档位、触发方式

选型里最关键的一条是“定时器触发 ADC + DMA 搬运”。如果让 CPU 在循环里手动读 ADC,采样间隔会被屏幕刷新和按键扫描拖得忽长忽短,画出来的波形抖动明显。用硬件触发加 DMA,采样节奏由定时器决定,CPU 只管处理已经采好的数据,波形稳定性会好很多。

2.3 数据流:一个采样点从引脚到屏幕的路径

把链路理清楚,后面看代码就不会乱。一个采样点的完整旅程是这样的:

  1. 定时器按设定频率产生触发事件;
  2. ADC 收到触发,转换当前引脚电压,结果写入数据寄存器;
  3. DMA 检测到 ADC 数据就绪,自动把值搬到内存缓冲区adc_buf[];
  4. 缓冲区采满一批后触发中断(或主循环轮询标志位);
  5. 主逻辑对这批数据做触发判断、电压换算;
  6. 换算后的点交给屏幕驱动,画到对应像素位置。

这条链路里,第 3 步的 DMA 是性能关键,第 5 步的触发判断是“波形能不能稳住”的关键。很多人第一次做示波器,波形能出来但一直左右滚动,问题基本都出在触发逻辑上,后面避坑章节会细说。

3. 跑起来:编译、烧录与第一次看到波形

3.1 环境准备与工程打开

先把工具链备齐。这套工程是 Keil MDK 的工程格式(.uvprojx),所以你需要:

  • Keil MDK-ARM,版本 5 即可;
  • 对应 F103 系列的器件支持包(Device Family Pack);
  • 一个 ST-Link 或 J-Link 下载器;
  • 串口调试助手(可选,用来打印调试信息)。

装好之后直接双击工程文件打开。打开后先别急着编译,检查两个地方:一是Options for Target里的器件型号是否选到 STM32F103ZE,二是Debug选项卡里下载器是否识别到了芯片。这两处不对,后面全是白忙。

3.2 关键编译配置:时钟与下载设置

F103ZET6 的外部晶振通常是 8MHz,工程里system_stm32f10x.c会根据宏定义决定倍频到 72MHz。确认一下stm32f10x.h里的HSE_VALUE和你板子上的晶振一致,不一致会导致串口波特率、定时器周期全部偏掉,这种问题排查起来很费时间。

/* system_stm32f10x.c 中的时钟配置片段 */ #define SYSCLK_FREQ_72MHz 72000000 static void SetSysClockTo72(void) { /* 使能外部高速晶振 */ RCC->CR |= ((uint32_t)RCC_CR_HSEON); /* 等待 HSE 就绪,超时则停在这里便于排查 */ while((RCC->CR & RCC_CR_HSERDY) == 0) {} /* 配置 PLL:HSE 作为源,9 倍频,得到 72MHz */ RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9); /* 使能 PLL 并等待锁定 */ RCC->CR |= RCC_CR_PLLON; while((RCC->CR & RCC_CR_PLLRDY) == 0) {} /* 切换系统时钟到 PLL 输出 */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) {} }

这段代码的作用是把系统时钟从内部 8MHz 切到外部晶振倍频后的 72MHz。PLLMULL9就是 9 倍频,8MHz 乘 9 正好 72MHz。如果你的板子晶振不是 8MHz,这个倍频系数要跟着改,否则定时器算出来的采样率全是错的。几个while等待循环是死等,卡在这里说明晶振没起振,先查硬件焊接和负载电容。

3.3 烧录后第一次上电:屏幕和信号接入

编译通过、烧录成功后,板子重新上电。正常情况下屏幕会先显示界面框架,然后开始画波形。如果屏幕是白的或者花屏,先别怀疑程序,八成是屏幕驱动型号对不上,去HARDWARE/LCD里确认初始化序列和你的屏幕控制器是否匹配。

信号接入这一步要注意量程。ADC 引脚一般只能承受 0 到 3.3V,直接接超过这个范围的信号会烧引脚。测更高电压必须加分压电阻或者前端调理电路。第一次测试建议用板子自己的 3.3V 经过一个电位器分压,拧电位器看波形上下移动,这样最安全也最直观。

/* main.c 主循环的简化结构 */ int main(void) { /* 初始化各外设:时钟、屏幕、ADC、DMA、定时器、按键 */ SystemInit(); LCD_Init(); ADC_DMA_Init(); TIM_Trigger_Init(); KEY_Init(); while(1) { /* 等待一批采样完成 */ if (adc_batch_ready) { adc_batch_ready = 0; /* 触发判断,找到波形稳定显示的起点 */ trigger_index = Find_Trigger(adc_buf, BUF_SIZE); /* 换算电压并绘制 */ Draw_Waveform(adc_buf, trigger_index); } /* 扫描按键,处理档位和触发方式切换 */ Key_Process(); } }

主循环的逻辑很清晰:等数据、找触发点、画波形、处理按键。adc_batch_ready这个标志位由 DMA 传输完成中断置位,主循环只负责消费。这种“中断置标志、主循环处理”的写法在裸机里很常见,好处是中断服务函数尽量短,重活留给主循环,不会因为画图耗时影响采样。

4. 核心模块拆解:ADC 采样、触发判断与屏幕绘制

4.1 ADC 加 DMA 的连续采样配置

采样是整个示波器的源头,配不好后面全白搭。核心思路是让 ADC 在定时器触发下连续转换,DMA 在后台把结果搬走,CPU 完全不参与单点搬运。

/* ADC 与 DMA 初始化关键片段 */ void ADC_DMA_Init(void) { ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; /* 打开 ADC1 和 DMA1 时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* DMA 配置:从 ADC 数据寄存器搬到内存缓冲区 */ DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)adc_buf; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = BUF_SIZE; /* 外设地址不增,内存地址递增,每次搬一个半字 */ DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /* 循环模式,采满一轮自动从头开始 */ DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); /* ADC 配置:独立模式,外部触发,右对齐 */ ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); /* 规则通道 0,采样时间 55.5 周期 */ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); /* 打开 ADC 的 DMA 请求 */ ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); /* ADC 校准,别省这一步 */ ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }

几个参数值得单独说。DMA_Mode_Circular让 DMA 采满缓冲区后自动回到开头,这样采样永不停止,主循环随时能取最近一批数据。ADC_ExternalTrigConv_T2_CC2表示用定时器 2 的捕获比较通道 2 来触发转换,采样率就由这个定时器的频率决定。ADC_SampleTime_55Cycles5是采样保持时间,信号源内阻大的话要适当加长,否则采到的值偏低。

注意:ADC 校准那两行别删。不校准的话零点有偏移,画出来的波形整体上下平移,测小信号时误差很明显。

4.2 触发判断:让波形不再左右乱跑

波形能画出来但一直横向滚动,是新手做示波器最常见的翻车点。根因是每批数据的起始点相位不固定,屏幕从缓冲区第一个点开始画,自然每次都不一样。解决办法是做触发判断:在缓冲区里找一个满足条件的点作为绘制起点。

/* 上升沿触发:找到信号从阈值下方穿到上方的位置 */ u16 Find_Trigger(u16 *buf, u16 size) { u16 i; u16 threshold = (TRIGGER_LEVEL_HIGH + TRIGGER_LEVEL_LOW) / 2; for (i = 1; i < size / 2; i++) { /* 前一点在阈值下方,当前点在阈值上方,即为上升沿 */ if (buf[i-1] < threshold && buf[i] >= threshold) { return i; /* 返回触发点下标作为绘制起点 */ } } return 0; /* 没找到触发点就从头画,波形会滚动 */ }

threshold是触发电平,由用户通过按键调节,对应屏幕上那条水平触发线。循环只扫缓冲区前半段,是为了保证触发点后面还有足够的数据可以画满一屏。如果扫完都没找到符合条件的点,说明信号幅度没跨过触发电平,或者触发方式选错了(比如信号是下降沿为主却选了上升沿触发),这时返回 0,波形就会滚动,这也是判断触发是否生效的一个直观信号。

触发方式一般提供三种:上升沿、下降沿、自动。自动模式下即使没触发也强制刷新,适合观察无规律信号;上升沿和下降沿模式则要求必须找到触发点才更新,波形更稳但信号不满足条件时会停住。

4.3 屏幕绘制:把采样值映射成像素坐标

绘制这一步要把 12 位 ADC 值(0 到 4095)映射到屏幕的纵向像素范围。假设屏幕绘图区高度是DRAW_HEIGHT像素,映射关系是线性的。

/* 将 ADC 值换算成屏幕 Y 坐标并画点 */ void Draw_Waveform(u16 *buf, u16 start) { u16 x, y; u16 prev_y = 0; for (x = 0; x < DRAW_WIDTH; x++) { /* 取触发点之后的数据,越界则回绕 */ u16 idx = (start + x) % BUF_SIZE; u16 adc_val = buf[idx]; /* 线性映射:ADC 值域映射到屏幕高度,注意 Y 轴方向翻转 */ y = DRAW_BOTTOM - (u32)adc_val * DRAW_HEIGHT / 4096; /* 和上一个点连线,避免点太稀出现断点 */ if (x > 0) LCD_DrawLine(x - 1, prev_y, x, y); prev_y = y; } }

映射公式里DRAW_BOTTOM是绘图区底部的 Y 坐标,减去换算值是因为屏幕坐标系原点在左上角,Y 越大越靠下,而电压越高应该画得越靠上,所以要翻转。除以 4096 对应 12 位 ADC 的满量程。用LCD_DrawLine把相邻两点连起来,比单纯画点视觉上连续得多,尤其在采样点少于屏幕宽度时。

如果屏幕刷新慢导致波形闪烁,常见做法是只重绘波形区域,不整屏清屏,或者用一块内存做显存、画完一次性刷过去。F103 的 FSMC 刷屏速度还行,但整屏清屏加逐点绘制还是会有可见延迟,优化时优先考虑缩小重绘范围。

5. 避坑与排查:那些让波形出不来的常见问题

5.1 屏幕花屏或全白

现象是上电后屏幕没有正常界面,要么全白要么彩色条纹。原因通常是 LCD 初始化序列和实际屏幕控制器不匹配,或者 FSMC 的时序参数配得太快,屏幕跟不上。解决方法是先确认屏幕型号,去HARDWARE/LCD里找对应的初始化代码;如果型号对但还花屏,把 FSMC 读写时序里的地址建立、数据保持时间适当加大,宁可慢一点也要先点亮。

5.2 波形滚动停不下来

现象是波形能显示但一直横向移动,调触发档位也没用。原因有两个:一是触发判断函数没找到符合条件的点,一直返回 0;二是触发电平设得超出了信号实际幅度范围。解决方法是先把触发方式切到自动模式确认信号本身正常,再切回边沿触发,用按键把触发电平调到信号幅度的中间位置,观察是否稳定。如果信号本身幅度就很小,先检查前端有没有衰减。

5.3 采样率上不去或波形失真

现象是调高采样率后波形反而变差,或者高频成分明显丢失。原因是 ADC 采样时间设得太短,信号源内阻又大,采样保持电容没充到位。解决方法是把ADC_SampleTime从 55.5 周期往上调,同时确认信号源输出阻抗不要太高,必要时加一级电压跟随器做缓冲。另外要记住采样定理,想看清某个频率的信号,采样率至少要是它的两倍以上,实际工程里一般取五到十倍。

5.4 编译报错找不到器件或库文件

现象是换电脑或者换 Keil 版本后编译一堆红。原因是器件支持包没装,或者工程里库文件的相对路径变了。解决方法是先在 Keil 的 Pack Installer 里装上 F1 系列的支持包,再检查Options for Target里的Include Paths是否指向了正确的库目录。路径里尽量别用中文和空格,这也是血泪经验。

5.5 按键没反应或误触发

现象是按键按下去没反应,或者没按也乱跳。原因是按键消抖没做,或者 GPIO 上下拉配置和实际电路对不上。解决方法是软件上加 10 到 20 毫秒延时消抖,硬件上确认按键是接地上拉还是接电源下拉,把 GPIO 模式配成对应的输入方式。如果用了外部中断,还要注意中断触发边沿设置。

6. 二次开发:换屏幕、加功能与验证方法

6.1 换一块屏幕要动哪些地方

换屏幕是这套程序最常见的改造需求。核心是把HARDWARE/LCD里的驱动换掉,但接口要保持一致。建议先定义一组统一的绘图接口,比如LCD_Init、LCD_DrawPoint、LCD_DrawLine、LCD_Clear,然后针对新屏幕实现这几个函数,主逻辑一行都不用改。

/* 统一的绘图接口,换屏幕时只改实现不改调用 */ void LCD_DrawPoint(u16 x, u16 y, u16 color); void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2); void LCD_Clear(u16 color); void LCD_Init(void);

如果新屏幕分辨率变了,DRAW_WIDTH和DRAW_HEIGHT这两个宏要跟着改,映射公式里的除数也要同步。分辨率提高后刷新压力变大,可以考虑降低波形刷新频率,或者只刷新变化区域。

6.2 加一个频率测量功能

在现有采样链路基础上加频率测量并不难。思路是对触发点之间的间隔做统计:连续找到两个上升沿触发点,它们之间的采样点数乘以采样周期,就是信号周期,取倒数就是频率。

/* 粗略频率测量:统计两个上升沿之间的采样点数 */ u32 Measure_Freq(u16 *buf, u16 size, u32 sample_rate) { u16 first = 0, second = 0; u16 threshold = (TRIGGER_LEVEL_HIGH + TRIGGER_LEVEL_LOW) / 2; u16 i, found = 0; for (i = 1; i < size; i++) { if (buf[i-1] < threshold && buf[i] >= threshold) { if (found == 0) { first = i; found = 1; } else { second = i; break; } } } if (second > first) return sample_rate / (second - first); /* 周期点数换算成频率 */ return 0; }

sample_rate是当前定时器设定的采样率,单位 Hz。这个方法测低频信号够用,但信号频率接近采样率一半时误差会变大,因为一个周期内的采样点太少。想测更高频率,要么提高采样率,要么改用测周法配合输入捕获。

6.3 改造后的验证习惯

每次改完驱动或者采样逻辑,我一般按固定顺序验证一遍:先用信号发生器或者板载 PWM 输出一个已知频率的方波,确认波形形状和频率读数对得上;再把幅度调小,看小信号下触发是否还稳;最后连续运行十几分钟,看会不会因为缓冲区溢出或者内存越界跑飞。这套流程能挡掉大部分低级错误。

从那以后我每次动采样相关的代码,都强制先用已知信号跑一遍再往下写功能,不然等一堆功能叠上去再回头查,根本分不清是哪一层出的问题。希望这份拆解能帮你把这块 F103 板子上的示波器跑起来,也顺手改成自己想要的样子。

本文还有配套的精品资源,点击获取

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

二手服务器搭建私有AI平台:从选模型到部署推理引擎全指南

聊到私有 AI 平台&#xff0c;我猜很多人和我一样&#xff0c;第一反应是"搞台好机器装个界面就算搭完了"。但真到自己动手&#xff0c;尤其用的还是一台二手服务器&#xff0c;事情远没有这么简单。 这是这个系列的第三篇。前面两篇我分别讲了怎么淘硬件、装机、做…

作者头像 李华
网站建设 2026/10/16 10:04:14

Flutter手势与点击事件全解析:从基础组件到手势竞技场与实战避坑

在 Flutter 里做点击事件&#xff0c;看起来是个入门得不能再入门的话题&#xff0c;但实际写起来&#xff0c;很多人都会在“为什么 onTap 没反应”“为什么水波纹不见了”“为什么拖动和列表滚动打架”这类问题上卡壳。尤其是当你开始做手势组合、自定义交互的时候&#xff0…

作者头像 李华
网站建设 2026/10/16 10:01:50

SQL注入过滤绕过实战:BabySQL双写注入完整题解

这道题我第一次做的时候&#xff0c;确实被“Baby”两个字骗了。名字看着像给新手练手的入门题&#xff0c;结果我在用户名框里输入 or 11# &#xff0c;页面不但没有出现预期中的登录成功&#xff0c;反而整个状态都不对劲。卡了一会儿才发现&#xff0c;它并不是一道普通的…

作者头像 李华
网站建设 2026/10/16 10:01:39

微信接入OpenClaw:把聊天框变成邮箱控制中心

微信接入OpenClaw这个标题&#xff0c;说白了就是把我日常刷得最频繁的聊天框&#xff0c;变成了控制中心&#xff0c;而邮箱成了被遥控的那个终端。听着有点夸张&#xff0c;但实际用起来确实有一种“手机上的微信突然会自己办事了”的感觉。这篇文章我把整个项目从需求、架构…

作者头像 李华
网站建设 2026/10/16 10:01:08

Dual Attention注意力机制深度解析:原理、代码与实践

最近在读语义分割方向的论文&#xff0c;正好看到Dual Attention这篇&#xff0c;读完之后有种“原来还可以这么直接算”的感觉。所谓Dual Attention&#xff0c;核心是把注意力机制拆成两个维度分别处理&#xff1a;一个管空间位置之间的关联&#xff0c;一个管通道之间的关联…

作者头像 李华
网站建设 2026/10/16 10:00:57

STM32+RS485+DMA实现Modbus RTU从站,告别串口中断风暴

简介&#xff1a;面向STM32嵌入式开发者的RS485与Modbus通信实战资料包&#xff0c;基于STM32F103ZET6平台讲解如何借助DMA实现高效主从数据交互&#xff0c;覆盖RS485接口配置、Modbus RTU读写报文&#xff08;0x03/0x06功能码&#xff09;解析与处理等核心环节&#xff0c;适…

作者头像 李华