STM32多通道ADC规则转换与DMA配置详解及软件滤波实战
2026/6/5 13:20:03 网站建设 项目流程

1. 项目概述与核心痛点

折腾了好几天,终于把STM32的多通道ADC规则转换配合DMA给跑通了。这事儿说起来简单,但真动手配置的时候,看着手册里ADC的扫描模式、连续转换、规则通道序列,再叠加上DMA的传输方向、循环模式、数据对齐,脑子是真有点发懵。网上能找到的例程,要么是单通道轮询,要么就是两三个通道的简单扫描,而且很多例子用的ADC通道还不连续,想找个完整的、用同一个ADC模块连续采集8个通道,并且用DMA自动搬运数据的参考,还真不容易。更别提还得把数据处理好,稳定地显示出来了。这次我用的STM32F103,目标是实现ADC1的通道8到通道15,总共8个通道的连续自动采集,数据通过DMA1的通道1直接送到内存数组里,然后在主循环里做一波“去极值取平均值”的软件滤波,最后把算好的值扔给LCD显示。实测下来,在万利的开发板上,显示的数值比它自带的例程要稳定平滑得多,波动明显小了。下面我就把整个从原理到踩坑,再到代码实现的完整过程拆开揉碎了讲清楚,如果你也在为多通道ADC采集不稳定、CPU被占用率高而头疼,这篇笔记应该能帮到你。

2. 整体方案设计与核心思路拆解

2.1 为什么选择“规则组+扫描+连续+DMA”模式?

STM32的ADC功能相当灵活,但也因此让初学者容易迷惑。面对多通道采集,我们有几个关键选择需要做对。

首先是转换模式:单次转换还是连续转换?单次转换模式下,ADC转换一次后就停止,需要软件再次触发。这对于低功耗或非连续采样的场景合适,但我们的目标是实时监控8个通道,需要不间断的数据流。因此,连续转换模式(ADC_ContinuousConvMode = ENABLE)是必选的。一旦启动,ADC就会马不停蹄地按照既定序列一个接一个地转换。

其次是通道组织方式:STM32的ADC通道可以分配到“规则组”或“注入组”。规则组可以理解为一个常规的、按顺序执行的采集任务队列;注入组则像是一个可以插队的高优先级任务。对于我们的8通道匀速采集需求,规则组(Regular Channel)完全够用,配置起来也更直观。我们需要告诉ADC一个规则通道序列,比如第1个转通道8,第2个转通道9,以此类推。

接着是扫描模式:这是实现多通道自动切换的核心。当扫描模式(ADC_ScanConvMode)使能后,ADC会按照我们配置好的规则通道序列,自动地、依次对每个通道进行转换。如果不使能扫描,即使你配置了多个规则通道,ADC也只会转换序列中的第一个通道就停下。所以,扫描模式必须打开。

最后是数据搬运策略:这是解放CPU的关键。每个通道转换完成后,结果会存放在一个固定的数据寄存器(ADC_DR)里。如果我们用CPU去轮询或中断读取这个寄存器,在8通道连续高速转换下,CPU基本就被栓死了,啥也别想干。直接内存访问(DMA)就是为了解决这个痛点而生的。我们可以配置DMA,让它在每次ADC转换完成时,自动把ADC_DR里的数据搬运到我们指定的内存数组里。整个过程不需要CPU干预,实现了数据采集和程序逻辑的完全并行。

总结一下核心链路:ADC工作在独立模式连续转换扫描模式下,按预设的规则通道序列循环采集。每转换完一个通道,硬件自动触发DMA请求,DMA控制器将数据从ADC外设搬运到内存缓冲区。CPU只需要定期去处理内存里已经积累好的数据即可。这个方案效率最高,实时性最好。

2.2 关键外设与引脚规划

我使用的芯片是STM32F103系列,具体型号根据开发板而定。ADC1的通道8到15对应的GPIO引脚如下:

  • ADC1_IN8->PB0
  • ADC1_IN9->PB1
  • ADC1_IN10->PC0
  • ADC1_IN11->PC1
  • ADC1_IN12->PC2
  • ADC1_IN13->PC3
  • ADC1_IN14->PC4
  • ADC1_IN15->PC5

注意:STM32的ADC通道与GPIO引脚是复用的。在初始化时,必须先将这些GPIO引脚的模式配置为模拟输入(GPIO_Mode_AIN)。这能关闭引脚内部的上下拉电阻,提供最高的输入阻抗,确保ADC采样电压的准确性,尤其是对于高内阻的信号源至关重要。

DMA方面,STM32F103的ADC1固定使用DMA1的通道1。这是一个硬件上的映射关系,不能更改。这简化了我们的配置,只需要初始化DMA1_Channel1,并将其源地址指向ADC1的数据寄存器地址(ADC1_DR_Address)即可。

3. 关键模块配置详解与避坑指南

3.1 GPIO与时钟配置:地基必须打牢

任何外设使用前,时钟是第一步。STM32的外设都需要对应的时钟总线使能。

void RCC_Configuration(void) { // 开启GPIOB和GPIOC的时钟(因为ADC通道用的引脚在这两个端口上) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); // 开启ADC1的时钟,ADC挂载在APB2总线上 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 开启DMA1的时钟,DMA挂载在AHB总线上 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); }

GPIO配置相对简单,但模式一定要选对:

void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; // 配置PB0, PB1为模拟输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOB, &GPIO_InitStructure); // 配置PC0, PC1, PC2, PC3, PC4, PC5为模拟输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5; GPIO_Init(GPIOC, &GPIO_InitStructure); }

避坑点1:时钟顺序。理论上先开哪个时钟影响不大,但良好的习惯是先开启GPIO时钟,再开启外设(ADC、DMA)时钟。有些高级型号的芯片,外设时钟依赖GPIO时钟的某些设置。

避坑点2:模拟输入模式。务必选择GPIO_Mode_AIN。如果错误地配置为上拉或下拉输入,内部电阻会与外部信号源形成分压,导致采样值严重偏离真实电压。

3.2 DMA配置:数据搬运的“自动驾驶”

DMA的配置是整个流程中最需要理解其含义的部分。配置不当,数据要么搬不动,要么搬错地方。

DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); // 复位DMA通道1,使其恢复到默认状态 DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; // 外设地址:ADC1数据寄存器 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_RCVTab; // 内存地址:自定义的数组 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 方向:从外设(ADC)读,写到内存 DMA_InitStructure.DMA_BufferSize = 160; // 缓冲区大小:这是关键! DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增,固定读ADC_DR DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增,数据依次存放 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度:半字(16位) DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 内存数据宽度:半字(16位) DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 模式:循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 优先级:高 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁止内存到内存模式(我们是用外设触发) DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); // 使能DMA通道

核心参数深度解析:

  1. DMA_BufferSize = 160:这是最容易出错的地方。这个值不是指字节数,而是指“传输次数”。我们的数据宽度是半字(16位,即2字节)。这里设置为160,意味着DMA会进行160次传输。为什么是160?因为我想为每个通道保留20个样本做后续滤波。8个通道 * 20个样本/通道 = 160次传输。DMA会忠实地搬运160次,每次搬运一个通道的转换结果(2字节),填满整个ADC_RCVTab数组。完成后,由于是循环模式,它会回到数组开头,覆盖旧数据,如此往复。这个大小决定了你内存缓冲区的大小,必须匹配。

  2. DMA_Mode_Circular(循环模式):这是实现连续不间断采集的灵魂。在循环模式下,当DMA传输完设定的次数(BufferSize)后,硬件会自动将传输计数器重置为初始值,并从头开始新一轮的传输。这意味着只要ADC不停,DMA就会一直循环往复地向内存的同一块缓冲区写入数据,我们永远能读到“最新”的160个数据。如果设置为普通模式(DMA_Mode_Normal),传输160次后DMA就停止了,需要软件重新配置或使能,无法实现真正的后台连续采集。

  3. 地址递增控制PeripheralInc必须设为Disable,因为ADC转换结果永远只放在ADC1_DR这一个固定的寄存器里。MemoryInc必须设为Enable,这样每次DMA搬运后,目标内存地址会自动增加2字节(半字),数据才能按顺序存放到数组的不同位置。

  4. DMA_M2M_Disable:这个模式是用于内存块之间的复制,由软件触发。我们的传输是由ADC转换完成这个硬件事件触发的,所以必须禁用M2M模式。

3.3 ADC配置:多通道采集的核心引擎

ADC的配置需要将之前讨论的模式选择具体化。

ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // ADC1独立工作,不与其他ADC同步 ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 启用扫描模式(多通道必备) ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 启用连续转换模式 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 使用软件触发,而非外部引脚或定时器 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐(推荐,便于阅读) ADC_InitStructure.ADC_NbrOfChannel = 8; // 规则通道序列的长度,即要转换的通道数量 ADC_Init(ADC1, &ADC_InitStructure); // 配置规则通道序列:顺序和采样时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 2, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 4, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 5, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_13, 6, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 7, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 8, ADC_SampleTime_239Cycles5);

关键配置与经验:

  1. ADC_NbrOfChannel:这个数字必须与你后面调用ADC_RegularChannelConfig配置的通道数量严格一致。它告诉ADC内核,在扫描时一共要转换几个通道。如果这里填8,但你只配置了7个通道,ADC的行为是未定义的,可能导致数据错乱。

  2. 规则通道序列ADC_RegularChannelConfig函数的第三个参数是“序列中的排名(Rank)”,从1开始。这个排名决定了通道在扫描中的顺序。顺序可以任意编排,不一定要和通道号一致。例如,你可以让通道15第一个转换(Rank=1),通道8最后一个转换(Rank=8)。这在实际项目中很有用,比如你可以把最需要快速响应的信号通道排在前面。

  3. 采样时间(ADC_SampleTime_239Cycles5:这是ADC对输入信号进行采样的时间长度,时钟周期数越多,采样越充分,结果越准确,但转换速度越慢。239.5个周期是F103系列在标准库中提供的最长采样时间,适用于高输出阻抗的信号源,能保证采样电容被充分充电到输入电压。如果你的信号源内阻很低(比如直接来自运放输出),可以适当减少采样时间(如55.528.5个周期)来提高整体采样率。总转换时间 = 采样时间 + 12.5个周期(固定转换时间)。计算采样率时需要考虑8个通道的总时间。

  4. 外部触发:这里选择了None,意味着我们通过软件命令(ADC_SoftwareStartConvCmd)来启动第一次转换。由于开启了连续转换模式,第一次启动后,ADC就会永不停歇地循环转换,不再需要后续触发。如果你需要精确的定时采样,比如每1ms采集一次,那么应该选择外部触发,并配置一个定时器来产生触发信号。

使能DMA与校准:

ADC_DMACmd(ADC1, ENABLE); // 允许ADC在转换完成后产生DMA请求 ADC_Cmd(ADC1, ENABLE); // 使能ADC1 // ADC上电后或环境变化后,必须进行校准! ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); // 等待复位校准完成 ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件启动连续转换

避坑点3:校准的必要性。ADC模块内部有偏移和增益误差,校准过程就是让ADC自身测量这些误差并在后续转换中进行补偿。每次ADC使能后,都必须执行一次校准,否则初始的几十个采样值可能会严重偏离真实值。这是很多新手忽略但至关重要的一步。

避坑点4:启动顺序。务必先使能ADC的DMA请求(ADC_DMACmd),再使能ADC模块(ADC_Cmd),最后再启动转换。有些例程顺序混乱,可能导致DMA无法正常启动。

4. 数据处理与软件滤波实战

4.1 内存数据结构与DMA填充逻辑

DMA搬运的目标是一个我们定义在内存中的数组。理解这个数组的结构是如何被DMA填充的,是正确读取数据的前提。

#define ADC_CHANNEL_NUM 8 #define SAMPLE_PER_CH 20 vu16 ADC_RCVTab[ADC_CHANNEL_NUM * SAMPLE_PER_CH]; // 160个元素的数组

DMA的搬运是线性的、连续的。假设ADC按照我们配置的顺序(通道8, 9, 10, ..., 15)进行转换。那么DMA搬运到ADC_RCVTab数组中的数据流将是这样的:[Ch8_Sample1], [Ch9_Sample1], [Ch10_Sample1], ..., [Ch15_Sample1], [Ch8_Sample2], [Ch9_Sample2], ..., [Ch15_Sample2], ...直到填满160个位置。然后循环模式开启,从ADC_RCVTab[0]开始覆盖。

因此,对于第i个通道(i从0开始,对应物理通道8)的第j个样本(j从0开始),它在数组中的索引位置是:index = j * ADC_CHANNEL_NUM + i例如,通道9(i=1)的第3个样本(j=2),索引是2*8 + 1 = 17

4.2 “去极值取平均值”滤波算法实现

直接从ADC读取的原始值通常带有噪声,表现为数值上的微小跳动。简单的算术平均可以平滑,但无法抵抗偶尔出现的突发性干扰(尖峰)。我的策略是:先“去极值”,再“取平均”。

// 对指定通道的多个样本进行滤波 vu16 average(vu16* buffer, u8 channel) { vu32 sum = 0; vu16 max_val = 0, min_val = 0xFFFF; vu16 temp_array[SAMPLE_PER_CH]; // 1. 提取该通道的所有样本到临时数组 for(u8 i = 0; i < SAMPLE_PER_CH; i++) { temp_array[i] = buffer[i * ADC_CHANNEL_NUM + channel]; } // 2. 第一轮循环,找出最大值和最小值(极值) for(u8 i = 0; i < SAMPLE_PER_CH; i++) { if(temp_array[i] > max_val) max_val = temp_array[i]; if(temp_array[i] < min_val) min_val = temp_array[i]; } // 3. 第二轮循环,累加时剔除最大值和最小值各一个 u8 count = 0; u8 max_removed = 0, min_removed = 0; for(u8 i = 0; i < SAMPLE_PER_CH; i++) { if((temp_array[i] == max_val) && (max_removed == 0)) { max_removed = 1; // 丢弃第一个最大值 continue; } if((temp_array[i] == min_val) && (min_removed == 0)) { min_removed = 1; // 丢弃第一个最小值 continue; } sum += temp_array[i]; count++; } // 4. 计算剩余样本的平均值 if(count > 0) { return (vu16)(sum / count); } else { return 0; // 理论上不会发生,安全处理 } }

算法精讲与优化建议:

  1. 为什么分两次循环?第一次循环必须找出全局的最大最小值。如果边找边加边剔除,逻辑会非常复杂,容易出错。分两次循环虽然多遍历一次数组,但对于20个样本来说,开销微乎其微,代码清晰度和可靠性大大提升。

  2. 剔除策略:这里采用的是“剔除一个最大值和一个最小值”。对于高斯白噪声,这种方法能有效抑制偶然出现的野值。如果你的信号干扰更严重,可以考虑剔除头尾各两个值,但前提是你的样本数(SAMPLE_PER_CH)要足够多,否则会损失太多有效数据。

  3. vu16vu32:注意累加和sum要用32位变量(vu32)。因为16位的ADC值(最大4095)乘以20后,很容易超过65535(16位最大值)。使用32位变量可以避免溢出。

  4. 实时性权衡SAMPLE_PER_CH(每个通道的样本数)是滤波效果和实时性的权衡。样本数越多,滤波效果越好,但你需要等待DMA收集够这么多样本才能计算一次有效值,系统响应会变慢。在我的应用中,20个样本在72MHz系统时钟、ADC采样时间较长的设置下,更新率依然能满足LCD显示的需求(人眼分辨不出延迟)。你可以根据你的ADC转换速度和系统要求调整这个值。

4.3 主循环中的数据提取与显示

主循环的工作就变得非常清晰和轻松了:

while(1) { value1 = average(ADC_RCVTab, 0); // 通道8 value2 = average(ADC_RCVTab, 1); // 通道9 // ... 同理获取value3到value8 // 将数值转换为字符串显示到LCD(此处以value3为例) u8 num1 = value3 % 10; u8 num2 = (value3 / 10) % 10; u8 num3 = (value3 / 100) % 10; u8 num4 = value3 / 1000; // 数字转字符,考虑十六进制显示(如果值大于9) display[3] = (num1 > 9) ? (num1 - 10 + 'A') : (num1 + '0'); display[2] = (num2 > 9) ? (num2 - 10 + 'A') : (num2 + '0'); display[1] = (num3 > 9) ? (num3 - 10 + 'A') : (num3 + '0'); display[0] = (num4 > 9) ? (num4 - 10 + 'A') : (num4 + '0'); write_string(display); // 自定义的LCD写字符串函数 delay(); // 一个简单的延时,控制显示刷新率,避免LCD刷新过快 }

避坑点5:数据竞争问题。这里有一个潜在的隐患:DMA在后台不停地修改ADC_RCVTab数组,而主循环中的average函数正在读取它。如果DMA在average函数读取到一半时修改了数组,我们可能会读到“半新半旧”的一组数据,导致计算错误。对于STM32F103,由于其是Cortex-M3内核,对16位半字的访问是原子的(不可分割的),而我们每次读取的就是一个16位的ADC值,所以在这种情况下是安全的。但是,如果处理更复杂的数据结构或者在多核/更高并发场景下,就需要使用临界区保护或双缓冲区技术来避免这个问题。一个简单的双缓冲区思路是:准备两个一样的数组BufferABufferB。DMA填满BufferA后产生中断,在中断里切换DMA的目标地址到BufferB,并设置一个标志位。主循环检测到标志位后,就去处理已经完整的BufferA数据。这样读写完全分离,绝对安全。

5. 调试技巧与常见问题排查

5.1 调试阶段的心得与工具

  1. 先验证单通道:不要一上来就搞8通道。先把DMA关掉,配置成单通道轮询模式,用万用表给一个已知电压(比如3.3V的一半),看看读回来的ADC值是否正确(理论上应该是4095/2≈2047左右)。这一步能排除GPIO配置、ADC基本配置、时钟、电压基准等问题。

  2. 再验证DMA搬运:开启单通道,并开启DMA,但设置为普通模式(DMA_Mode_Normal),BufferSize设为1。在启动ADC转换后,立即在调试器中观察ADC_RCVTab[0]的值是否和ADC数据寄存器一致。这一步验证DMA通道、地址、数据宽度配置是否正确。

  3. 最后上多通道和循环模式:前两步没问题后,再增加通道数,改为扫描模式,并将DMA改为循环模式,增大BufferSize。用调试器观察ADC_RCVTab数组,数据应该是按顺序、有规律地变化。

  4. 善用调试器的“实时查看”和“图形化”功能:像STM32CubeIDE或Keil MDK的调试器,可以实时刷新查看某个内存区域(如我们的数组)的值。更高级的是可以将一段内存的数据以波形图的形式显示出来。给不同通道输入不同频率或幅度的信号(比如用信号发生器或PWM加RC滤波产生),然后在图形化窗口看,哪个通道对应哪段数据就一目了然了。

5.2 常见问题速查表

现象可能原因排查步骤
ADC读取值始终为0或固定值1. GPIO未配置为模拟输入。
2. ADC或对应GPIO时钟未开启。
3. ADC未校准或校准未完成就读取。
4. 信号电压超出VDDA/VSSA范围。
1. 检查GPIO初始化代码。
2. 检查RCC配置代码。
3. 确保校准循环已完成(while循环退出)。
4. 用万用表测量输入引脚电压。
只有第一个通道有数据,后续通道为0或乱码1.ADC_ScanConvMode未使能。
2.ADC_NbrOfChannel设置错误,小于实际通道数。
3. 规则通道序列(Rank)配置错误或重复。
1. 确认ADC_InitStructure.ADC_ScanConvMode = ENABLE
2. 确认ADC_NbrOfChannel等于实际配置的通道数。
3. 检查ADC_RegularChannelConfig调用次数和Rank参数。
DMA不搬运数据,数组内容不变1. DMA时钟未开启。
2. DMA配置中源/目标地址错误。
3.ADC_DMACmd未使能。
4. DMA通道错误(ADC1必须用DMA1_Ch1)。
5.DMA_M2M模式被错误使能。
1. 检查RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE)
2. 检查PeripheralBaseAddr是否为ADC1_DR_Address
3. 确认在ADC初始化后调用了ADC_DMACmd(ADC1, ENABLE)
4. 核对通道。
5. 确认DMA_M2M = DISABLE
数据在数组中顺序错乱1. DMA内存地址递增(MemoryInc)未使能。
2.BufferSize计算错误,导致数据覆盖错位。
3. 读取数组索引计算逻辑错误。
1. 确认DMA_InitStructure.DMA_MemoryInc = ENABLE
2. 复核BufferSize= 通道数 × 每通道样本数。
3. 用调试器查看数组原始数据,验证DMA填充模式,调整读取索引公式。
ADC值不稳定,跳动大1. 采样时间太短,对高阻抗信号源采样不充分。
2. 电路噪声,电源或地线不干净。
3. 未进行软件滤波。
1. 增加ADC_SampleTime(如改为239Cycles5)。
2. 检查PCB布局,模拟部分电源加滤波电容,信号线远离数字噪声源。
3. 实施本文的软件滤波算法,并增加样本数。
程序运行一段时间后卡死1. DMA缓冲区溢出或访问越界(如果用了中断)。
2. 堆栈溢出(如果滤波函数内定义了大数组)。
3. 中断嵌套冲突。
1. 检查DMA中断标志位是否被正确清除。
2. 将大数组定义为全局变量或静态变量。
3. 合理设置中断优先级。

5.3 性能优化与扩展思考

  1. 提高采样率:如果觉得8个通道轮询一遍太慢,可以尝试减少每个通道的采样时间(前提是信号源驱动能力强),或者提高ADC的时钟(ADCCLK)。STM32F103的ADC时钟最高14MHz,通常由PCLK2分频得到。但注意,采样时间周期数变少,精度可能会下降。

  2. 使用注入组处理紧急信号:如果8个通道里有一个需要非常快速响应的信号(比如过流保护),可以把它配置到注入组。注入组可以打断规则组的转换,优先执行。规则组用于常规巡检,注入组用于处理突发事件。

  3. 定时器触发:要实现精确的等间隔采样,最佳实践是使用定时器(如TIM2/3/4)的TRGO输出作为ADC的外部触发源。将ADC_ExternalTrigConv配置为相应的定时器,然后设置定时器的自动重载值(ARR)和预分频(PSC)来控制采样频率。这样采样间隔由硬件精确保证,不受软件循环延迟的影响。

  4. 双缓冲区与中断处理:如前所述,在数据量很大或处理逻辑复杂时,使用DMA双缓冲区加传输完成中断是更稳健的方案。在DMA中断服务程序里切换缓冲区并设置数据就绪标志,主循环只需检查标志并处理数据,效率更高且无数据竞争风险。

整个项目调通后,看着LCD上稳定跳动的数字,确实有种豁然开朗的感觉。STM32的ADC和DMA配合起来,一旦配置正确,其稳定性和效率是轮询方式无法比拟的。最关键的是要理解每个配置参数背后的硬件行为,特别是DMA的循环模式、缓冲区大小以及ADC的扫描和连续转换模式是如何协同工作的。希望这篇详细的记录,能帮你跨过从“看例程”到“自己设计”的这道坎。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询