HAL库stm32g431rbt6,怎么写一个一秒内捕捉adc的最大值和最小值
时间: 2025-03-17 20:13:41 浏览: 83
<think>好的,我现在需要解决用户的问题:如何在HAL库和STM32G431RBT6上编写代码,以在一秒内捕捉ADC的最大值和最小值。用户之前询问过如何检测数据增加,现在的问题更具体,涉及到ADC采样、极值捕捉和时间窗口的处理。我需要结合之前的回答,考虑中断、定时器、滤波方法等,同时确保实时性和准确性。
首先,用户使用的是HAL库和STM32G431RBT6,所以我需要基于HAL的函数来设计。捕捉极值通常需要连续采样,并在一定时间内比较更新最大值和最小值。这里的一秒时间窗口是关键,需要用定时器来划分时间段。
接下来,用户可能需要知道如何配置ADC和定时器。ADC可能需要设置为连续扫描模式或使用DMA来提高效率,尤其是当采样频率较高时。定时器用来触发ADC的采样,或者用来产生中断,以每秒一次处理数据。
考虑到实时性,可能需要双缓冲机制:一个缓冲区用于当前秒的采样,另一个用于处理前一秒的数据。但用户只需要最大值和最小值,可能不需要存储所有数据,而是实时比较,这样更节省资源。但需要确保在每秒结束时记录极值,并重置比较变量。
另外,如何同步时间窗口?比如,使用定时器中断每秒触发一次,在中断服务函数中记录当前的最大最小值,并重置它们。同时,主循环或ADC回调中持续更新当前的极值。
需要注意变量在中断和主程序之间的共享问题,需要使用volatile关键字,或者临界区保护,防止数据竞争。比如,在中断中更新标志位,主循环中处理数据。
可能还需要考虑ADC的采样率。假设每秒采样N次,定时器配置为1秒触发一次中断,在中断中处理数据。但具体如何配置定时器和ADC的触发源,需要查阅STM32G4的参考手册和HAL库的例程。
另外,用户可能需要滤波处理,比如去抖动,但问题中没有提到噪声,所以可能直接比较即可。但如果有噪声,可能需要滑动平均或阈值滤波,但用户的需求是极值,可能需要保留原始数据中的峰值,所以滤波可能不适用,或者需要根据具体情况调整。
现在,如何组织代码结构?可能需要以下步骤:
1. 配置ADC以连续转换模式,使用定时器触发采样,或者使用定时器中断来启动ADC采样。
2. 配置一个定时器,每1秒产生一次中断,用于标志时间窗口结束。
3. 在ADC转换完成中断中,读取采样值,并更新当前的最大值和最小值。
4. 在定时器中断中,将当前的最大最小值保存到变量中,并重置最大值和最小值,为下一个周期做准备。
5. 确保共享变量(如最大值、最小值)的访问安全,可能需要禁用中断或使用原子操作。
但具体实现时,HAL库的ADC有多种模式,比如轮询、中断、DMA。使用DMA可以自动传输数据到缓冲区,适合高速采样。例如,配置ADC使用DMA循环模式,定时器触发ADC的采样,这样每个定时器周期触发一次ADC采样,DMA将数据传到缓冲区。当缓冲区满或定时器到达1秒时,处理数据。
或者,使用定时器每隔一段时间(比如1ms)触发ADC采样,然后在1秒后统计所有采样值中的极值。这种方法需要足够快的采样率,但具体取决于定时器的配置。
另一个方法是使用ADC的连续转换模式,无需外部触发,以最大速率采样,同时用另一个定时器来标志1秒的时间窗口。在这种情况下,ADC的数据可能非常快,需要DMA来搬运数据到缓冲区,然后在定时器中断时处理缓冲区中的数据,找出极值。但缓冲区的大小需要足够大,或者使用循环缓冲区,这可能占用较多内存。
考虑到资源限制,可能更高效的方式是在每次ADC采样后立即比较更新最大值和最小值,而定时器每秒中断一次来记录这些值并重置。这样不需要大缓冲区,实时更新极值。
因此,具体步骤可能如下:
- 初始化ADC,配置为连续转换模式,使用中断或DMA。
- 初始化一个基本定时器,每1秒产生一次中断。
- 在ADC转换完成的中断服务程序(或DMA传输完成中断)中,读取ADC值,并与当前的最大最小值比较,更新它们。
- 在定时器的中断服务程序中,将当前的最大最小值保存到另外的变量中,并重置max和min为初始值(比如,min设为ADC最大值,max设为0),以便下一周期重新比较。
- 主程序可以读取保存的max和min值进行处理,比如发送到串口或显示。
需要注意的问题:
- ADC的采样率是否足够高,以在1秒内捕捉到信号的变化。需要根据信号频率选择合适的采样间隔。
- 在中断中处理比较操作的时间是否过长,影响其他中断响应。
- 变量的原子操作问题,比如在读取max和min时,可能被中断打断,导致数据不一致。需要使用volatile声明变量,或者在访问时暂时关闭中断。
代码示例结构可能包括:
- ADC和定时器的初始化函数。
- 中断服务函数处理定时器1秒标志和ADC转换完成。
- 全局变量存储当前max和min,以及定时器触发的标志。
例如,使用定时器6或7作为基本定时器,配置为1秒中断。ADC使用连续转换模式,由软件启动,或者由定时器触发。如果使用定时器触发ADC,可以精确控制采样率。
但STM32G4的ADC触发源可以选择定时器通道,比如使用TIM2的TRGO输出作为ADC的触发信号。这样,每次定时器产生更新事件时,触发ADC转换。这样,可以控制采样率,比如定时器配置为1kHz,即每秒采样1000次,然后在定时器中断中每秒统计一次。
或者,使用定时器每1秒触发一次ADC的采样序列,但这样只能得到一个采样值,显然不够。因此,正确的做法是使用另一个定时器以较高频率触发ADC采样,比如每秒触发1000次,这样在1秒内获得1000个样本,然后找出其中的极值。
但用户的需求是“一秒内捕捉最大值和最小值”,所以需要在每个1秒窗口内处理该时间段内的所有采样值的极值。
因此,可能的配置如下:
1. 配置一个定时器(如TIM2)为1kHz频率,触发ADC采样。这样每秒采样1000次。
2. ADC配置为外部触发模式,触发源为TIM2的更新事件。
3. ADC使用DMA循环模式,将采样值传输到缓冲区。例如,DMA缓冲区大小为1000,这样每秒填满一次。
4. 另一个定时器(如TIM6)配置为1秒中断,在中断中处理DMA缓冲区中的数据,找出最大值和最小值。
5. 或者在TIM2的更新中断中,每次采样后立即比较更新当前极值,TIM6每秒中断一次保存结果。
这种方法可能需要处理DMA缓冲区,或者在每次ADC转换完成后中断,进行实时比较。如果使用DMA循环模式,缓冲区会被不断覆盖,需要在处理时确保数据完整性。
但HAL库中,使用DMA的双缓冲模式可能更方便,或者使用普通DMA循环模式,并在定时器中断时处理已采集的数据。
不过,可能更简单的方法是,在每次ADC转换完成中断中读取值,并更新max和min,这样不需要DMA,但可能在高采样率下导致频繁中断,影响系统性能。
因此,需要根据采样率选择合适的方法。如果采样率不高(比如每秒1000次),中断方式可行;如果更高,可能需要DMA。
假设用户需要较高的采样率,使用DMA是更好的选择。例如,配置ADC为连续转换,由定时器触发,DMA循环传输到缓冲区。当定时器1秒中断时,遍历缓冲区中的最新数据(可能需要计算当前DMA的位置),找出极值。或者,使用双缓冲区,当一半填满时触发中断,处理前半部分,但这可能比较复杂。
或者,在1秒定时器中断中,停止DMA,读取当前缓冲区中的所有数据,找出极值,然后重启DMA传输。但这样会导致数据丢失,因此需要确保在读取时ADC暂停,可能影响实时性。
另一种思路是,在每次ADC转换完成时,通过DMA传输到内存,并在内存中维护一个足够大的缓冲区,每秒处理一次。但可能消耗较多内存。
综合考虑,如果资源允许,使用DMA循环缓冲区,每秒中断一次,遍历整个缓冲区找出极值。但需要注意,DMA的缓冲区大小需要等于或大于每秒的采样数,否则数据会被覆盖。例如,如果采样率是1kHz,缓冲区需要至少1000个元素,每秒处理一次。
但在实时处理中,可能需要在处理的同时继续采样,因此双缓冲区机制更可靠。但HAL库是否支持双缓冲,需要查阅。STM32G4的DMA支持双缓冲模式,可以通过配置实现。
综上,可能的实现步骤:
1. 配置定时器(如TIM2)以所需的采样率(比如1kHz)触发ADC。
2. 配置ADC以外部触发模式,使用TIM2作为触发源,连续转换,DMA双缓冲模式传输到两个缓冲区。
3. 配置另一个定时器(TIM6)每1秒产生中断。
4. 在TIM6的中断中,切换DMA缓冲区,处理前一个缓冲区的数据,找出最大最小值。
5. 这样,DMA在填充一个缓冲区时,另一个缓冲区可供处理,避免数据竞争。
不过,这可能比较复杂,对于刚接触的用户可能难以实现。因此,可能需要更简化的方法。
另一种简化方案:
- 使用ADC连续转换,DMA传输到单一缓冲区,大小足够存储1秒的数据(例如,1000个样本)。
- 配置TIM6每1秒中断一次,在中断中启动处理:停止ADC,读取缓冲区中的数据,找出极值,然后重新启动ADC和DMA。
但这样在读取和处理数据期间,ADC会停止,可能丢失部分数据。但如果是严格每秒处理一次,并且处理时间很短,可能可以接受。
或者,保持ADC和DMA一直运行,在TIM6中断时记录当前的DMA写入位置,然后处理从上次位置到当前位置的数据,找出极值。这需要跟踪DMA的当前指针,HAL库中可以通过__HAL_DMA_GET_COUNTER或类似函数获取剩余传输次数,从而计算已传输的数据量。
例如,DMA配置为循环模式,缓冲区大小为N,每次TIM6中断时,计算自上次中断以来新采样了多少数据,处理这部分数据中的极值。
这可能比较复杂,但能实现无缝采集和处理。
不过,对于用户的问题,可能更倾向于使用实时比较的方式,即在每次ADC采样后立即更新极值,而无需存储所有数据。这样无论采样率如何,只要在每次采样后比较即可,然后在1秒中断时记录结果并重置。
这种方法更节省内存,适合资源有限的嵌入式系统。
因此,具体步骤可能如下:
1. 配置ADC为连续转换模式,使用定时器触发(例如TIM2)以固定频率采样。
2. 启用ADC的DMA传输,配置为循环模式,每次转换完成后触发DMA传输到内存中的一个变量。
3. 在DMA传输完成中断(或半传输中断)中,读取ADC的值,并更新当前的最大最小值。
或者,不使用DMA中断,而是在每次转换完成后触发ADC中断,在中断服务程序中读取值并比较。
4. 配置TIM6为1秒中断,在中断服务程序中保存当前的max和min到其他变量,然后重置max和min,开始新的周期。
但是,如果使用DMA循环传输到单个变量,那么每次转换的值都会被覆盖,无法保存历史数据。因此,这种方法不可行。需要DMA传输到缓冲区,或者使用中断来逐个处理每个样本。
如果使用ADC的连续转换模式,并在每个转换完成后触发中断,在中断中读取值并更新极值,这可能在高采样率下导致过多的中断开销,影响系统性能。例如,1kHz的采样率意味着每1ms一次中断,这在STM32G4上可能可行,但更高的采样率可能导致问题。
因此,需要根据具体的采样率选择合适的方法。假设用户需要较高的采样率,比如每秒1000次,那么使用DMA传输到缓冲区,并在定时器中断中处理整个缓冲区会更高效。
现在,结合HAL库的具体函数,如何实现?
首先,初始化定时器TIM2作为ADC的触发源:
- 配置TIM2的PWM模式,产生周期性的触发信号,比如1kHz的更新事件,这样ADC每秒触发1000次采样。
然后,配置ADC:
- 使用外部触发,触发源为TIM2的TRGO事件。
- 启用连续转换模式,每次触发转换一个通道。
- 启用DMA传输,循环模式,目标地址为缓冲区,例如uint16_t adc_buffer[1000];
配置DMA为循环模式,这样当缓冲区填满后,DMA会自动从头开始覆盖旧数据。
然后,配置TIM6为1秒中断:
- 计算TIM6的预分频和自动重载值,使其每1秒触发一次更新中断。
- 在TIM6的中断服务程序中,遍历整个adc_buffer数组,找出最大值和最小值,并保存到全局变量中。
- 同时,可以重置缓冲区索引或标志,但因为是循环DMA,可能需要记录当前DMA的位置,或者处理整个缓冲区。
但问题在于,当DMA在循环模式下,缓冲区会被不断覆盖,如何确保在1秒内处理正确的数据?
实际上,当使用TIM2触发ADC以1kHz采样,DMA缓冲区设为1000,那么TIM6每秒中断时,DMA刚好填满缓冲区一次,此时处理整个缓冲区即可。但需要确保TIM6的中断与DMA传输同步,否则可能会有部分数据未被处理或重复处理。
或者,使用双缓冲区,当DMA传输到一半时触发中断,处理前半部分,后半部分由DMA继续填充。但需要结合TIM6的中断时间,这可能复杂。
另一种方法是在TIM6的中断中,停止DMA和ADC,读取当前DMA的传输计数,确定已传输的数据量,然后处理这些数据。但这样会导致采样停止,可能丢失数据。
因此,更可靠的方法是在每次TIM6中断时,遍历整个缓冲区,找出极值,无论缓冲区是否被覆盖。因为DMA是循环的,缓冲区中的数据总是最新的,所以当TIM6每秒触发时,缓冲区中的是最近1秒的数据(假设缓冲区大小等于或大于每秒采样次数)。
例如,如果采样率是1000Hz,缓冲区大小设为1000,那么每秒TIM6中断时,缓冲区刚好被填满一次,处理整个缓冲区即可得到最大值和最小值。
但如果采样率不是整数倍,或者定时器配置不准确,可能会有偏差。需要确保TIM2和TIM6的配置准确。
现在,编写代码的大致步骤:
1. 初始化TIM2,配置为1kHz触发频率,触发ADC转换。
- 计算TIM2的时钟频率,设置预分频和自动重载值,使其产生1kHz的更新事件。
- 配置TIM2的TRGO为更新事件。
2. 初始化ADC,配置为外部触发模式,触发源为TIM2。
- 使用ADC1,通道等根据硬件连接配置。
- 启用连续转换模式。
- 配置DMA为循环模式,传输到缓冲区adc_buffer,大小为1000。
3. 初始化TIM6,配置为1秒中断。
- 计算预分频和自动重载值,例如,如果系统时钟是170MHz,预分频设为17000-1,自动重载值为10000-1,这样定时器频率为170MHz / 17000 / 10000 = 1Hz,即1秒中断一次。
4. 在TIM6的中断服务程序中,遍历adc_buffer数组,找出最大值和最小值,保存到全局变量,并可选地重置缓冲区或标志。
5. 主循环中,可以读取全局的max和min值,进行处理,如发送到串口。
但需要注意,DMA可能在处理过程中覆盖缓冲区,因此当TIM6中断发生时,DMA可能正在写入缓冲区,导致数据不一致。为了避免这种情况,可以使用双缓冲区:配置DMA为双缓冲模式,当一半传输完成时触发中断,处理前半部分,同时DMA继续传输到后半部分。但需要结合时间窗口的处理,这可能比较复杂。
或者,在TIM6中断时,暂时禁用DMA,复制缓冲区内容到另一个数组,然后重新启用DMA。这样在处理复制的数组时,DMA继续填充原缓冲区,但可能会有短暂的数据丢失。不过,对于极值检测,可能可以接受,因为复制过程很快。
但HAL库中,双缓冲模式的配置可能较为复杂。因此,可能更简单的方式是使用足够大的缓冲区,例如,如果每秒采样1000次,缓冲区设为1000,在TIM6中断时,处理整个缓冲区,此时DMA可能已经重新开始覆盖缓冲区,但由于处理的是过去一秒的数据,所以此时缓冲区中的内容即为过去一秒的样本。
因此,代码示例可能如下:
```c
// 全局变量
volatile uint16_t adc_buffer[1000];
volatile uint16_t max_value = 0;
volatile uint16_t min_value = 0xFFFF;
// TIM6中断处理函数
void TIM6_DAC_IRQHandler(void) {
if (__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE) != RESET) {
__HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
// 遍历adc_buffer,寻找最大值和最小值
uint16_t current_max = 0;
uint16_t current_min = 0xFFFF;
for (int i = 0; i < 1000; i++) {
if (adc_buffer[i] > current_max) {
current_max = adc_buffer[i];
}
if (adc_buffer[i] < current_min) {
current_min = adc_buffer[i];
}
}
max_value = current_max;
min_value = current_min;
// 可以在这里将结果发送到串口或其他处理
printf("Max: %hu, Min: %hu\n", max_value, min_value);
}
}
// 主函数中初始化并启动定时器和ADC
int main(void) {
HAL_Init();
SystemClock_Config();
MX_TIM2_Init(); // 配置TIM2为1kHz触发
MX_ADC1_Init(); // 配置ADC1,外部触发,DMA
MX_TIM6_Init(); // 配置TIM6为1秒中断
HAL_TIM_Base_Start(&htim2); // 启动TIM2
HAL_TIM_Base_Start_IT(&htim6); // 启动TIM6中断
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 1000); // 启动ADC DMA传输
while (1) {
// 主循环可以处理其他任务
}
}
```
在此代码中,TIM2以1kHz触发ADC,DMA将转换结果传输到adc_buffer,循环覆盖。TIM6每秒中断一次,遍历整个缓冲区找出极值,并保存到max_value和min_value。主循环或其他地方可以使用这些值。
需要注意的问题:
1. ADC的采样时间需要足够快,以跟上1kHz的触发频率。
2. DMA传输的正确配置,确保数据正确传输到缓冲区。
3. 在TIM6中断处理函数中遍历数组的时间,如果数组很大(比如1000个元素),可能耗时较长,影响实时性。此时,可以考虑使用更高效的查找方法,或者在每次ADC转换完成时更新极值,而不是每秒遍历数组。
如果每次ADC转换完成时更新极值,可以避免遍历数组,但需要处理中断频率的问题。例如,在DMA传输完成中断中处理,但DMA传输完成中断是在整个缓冲区传输完成后触发,例如每1000次采样触发一次,这等同于每秒触发一次(如果采样率是1kHz)。这时,可以在DMA传输完成中断中处理整个缓冲区,找出极值。同时,TIM6的中断可能不再需要,因为DMA传输完成中断已经每秒触发一次。
因此,另一种方法:
配置DMA传输长度为1000,模式为正常(非循环),在DMA传输完成中断中处理缓冲区,然后重新启动DMA传输。这样,每次传输完成中断时,恰好是1秒的数据(假设触发频率是1kHz),处理数据并重启DMA。这种方法不需要TIM6,而是利用DMA传输完成中断作为1秒的标记。
代码结构如下:
```c
// 全局变量
volatile uint16_t adc_buffer[1000];
volatile uint16_t max_value = 0;
volatile uint16_t min_value = 0xFFFF;
// DMA传输完成中断回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
// 在传输完成时处理数据
uint16_t current_max = 0;
uint16_t current_min = 0xFFFF;
for (int i = 0; i < 1000; i++) {
if (adc_buffer[i] > current_max) {
current_max = adc_buffer[i];
}
if (adc_buffer[i] < current_min) {
current_min = adc_buffer[i];
}
}
max_value = current_max;
min_value = current_min;
// 重新启动DMA传输
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 1000);
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_TIM2_Init(); // 配置TIM2为1kHz触发
MX_ADC1_Init(); // 配置ADC1,外部触发,DMA
HAL_TIM_Base_Start(&htim2); // 启动TIM2
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 1000); // 启动ADC DMA传输
while (1) {
// 主循环可以处理其他任务,或通过max_value和min_value获取结果
}
}
```
这种方法利用DMA传输完成中断,每次传输1000个样本后触发中断,处理数据并重启DMA。这样,每次处理的时间间隔是1秒(假设TIM2触发频率是1kHz)。但需要注意,DMA传输模式必须设置为正常模式(非循环),并在每次传输完成后手动重启,否则无法保证精确的1秒间隔。
此方法的优点是不需要额外的定时器,但需要确保TIM2的触发频率和DMA传输长度严格对应1秒的采样数。例如,1kHz触发频率下,1000次采样正好是1秒。
此外,需要确保在DMA传输完成中断中处理数据的时间足够短,避免影响下一次DMA传输的启动。如果处理时间过长,可能导致ADC采样间隔不准确,或丢失数据。
综上,用户需要根据具体的采样率和处理需求选择合适的方法。如果希望每次采样后立即更新极值,而不是每秒处理一次,可以使用实时比较的方法,在ADC转换完成中断中更新max和min,并在1秒定时器中断中记录结果。
例如:
```c
volatile uint16_t current_max = 0;
volatile uint16_t current_min = 0xFFFF;
volatile uint16_t saved_max = 0;
volatile uint16_t saved_min = 0xFFFF;
// ADC转换完成中断回调
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
uint16_t value = HAL_ADC_GetValue(hadc);
if (value > current_max) {
current_max = value;
}
if (value < current_min) {
current_min = value;
}
}
// TIM6 1秒中断处理
void TIM6_DAC_IRQHandler(void) {
if (__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE)) {
__HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
saved_max = current_max;
saved_min = current_min;
current_max = 0;
current_min = 0xFFFF;
}
}
int main(void) {
// 初始化代码...
// 启动ADC在中断模式,单次转换,由TIM2触发
HAL_ADC_Start_IT(&hadc1);
HAL_TIM_Base_Start(&htim2);
HAL_TIM_Base_Start_IT(&htim6);
while(1) {
// 使用saved_max和saved_min
}
}
```
在这种方法中,每次ADC转换完成后触发中断,在中断中更新current_max和current_min。TIM6每秒中断一次,保存这两个值到saved_max和saved_min,并重置current_max和current_min为初始值。这种方法适用于较低的采样率,因为每次ADC转换都会触发中断,高采样率时可能导致系统负担过重。
总结,用户需要根据采样率、处理时间和系统资源选择合适的方法。如果采样率较高,DMA结合定时器中断处理缓冲区的方法更高效;如果采样率较低,实时中断更新极值的方法更简单。</think>在STM32G431RBT6上使用HAL库实现1秒内捕捉ADC最大值和最小值的步骤如下:
---
### **1. 硬件配置**
- **ADC配置**:选择需采样的通道(如ADC1_IN1),设置连续扫描模式
- **定时器配置**:
- **TIM2**:用于触发ADC采样(例如1kHz采样率,需与ADC时钟匹配)
- **TIM6**:用于生成1秒时间基准中断
- **DMA配置**:启用ADC的DMA循环传输,避免CPU频繁中断
---
### **2. 核心代码实现**
#### **2.1 全局变量定义**
```c
#define ADC_BUF_SIZE 1000 // 1秒数据量(假设采样率1kHz)
volatile uint16_t adc_buffer[ADC_BUF_SIZE];
volatile uint16_t max_val = 0;
volatile uint16_t min_val = 0xFFFF;
volatile uint8_t data_ready = 0;
```
#### **2.2 ADC与定时器初始化**
```c
// TIM2配置(触发ADC采样)
void MX_TIM2_Init(void) {
htim2.Instance = TIM2;
htim2.Init.Prescaler = 17000-1; // 假设系统时钟170MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 100-1; // 1kHz触发频率
HAL_TIM_Base_Init(&htim2);
}
// TIM6配置(1秒中断)
void MX_TIM6_Init(void) {
htim6.Instance = TIM6;
htim6.Init.Prescaler = 17000-1;
htim6.Init.Period = 10000-1; // 1秒中断
HAL_TIM_Base_Init(&htim6);
}
// ADC1初始化
void MX_ADC1_Init(void) {
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T2_TRGO;
HAL_ADC_Init(&hadc1);
}
```
#### **2.3 DMA传输回调函数**
```c
// DMA传输完成中断回调
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
data_ready = 1; // 标记数据就绪
}
```
#### **2.4 定时器中断处理**
```c
// TIM6中断服务程序
void TIM6_DAC_IRQHandler(void) {
HAL_TIM_IRQHandler(&htim6);
if (data_ready) {
// 寻找极值
uint16_t tmp_max = 0, tmp_min = 0xFFFF;
for (int i=0; i<ADC_BUF_SIZE; i++) {
if (adc_buffer[i] > tmp_max) tmp_max = adc_buffer[i];
if (adc_buffer[i] < tmp_min) tmp_min = adc_buffer[i];
}
max_val = tmp_max;
min_val = tmp_min;
data_ready = 0;
}
}
```
#### **2.5 主函数初始化**
```c
int main(void) {
HAL_Init();
SystemClock_Config();
MX_TIM2_Init();
MX_TIM6_Init();
MX_ADC1_Init();
HAL_TIM_Base_Start(&htim2);
HAL_TIM_Base_Start_IT(&htim6);
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUF_SIZE);
while (1) {
// 主循环可通过max_val/min_val获取极值
}
}
```
---
### **3. 关键优化点**
1. **DMA双缓冲**:使用`HAL_ADC_Start_DMA(&hadc1, buf1, buf2, size)`避免数据覆盖
2. **实时极值更新**:在DMA半传输/全传输中断中直接比较数据
3. **硬件滤波**:配置ADC内置硬件滤波器(`hadc.Init.SamplingTime`)
---
### **4. 注意事项**
- ADC时钟需与定时器触发频率匹配
- 中断优先级:DMA中断优先级应高于定时器中断
- 临界区保护:若多任务访问共享变量,需使用`__disable_irq()`/`__enable_irq()`
---
### **代码流程图**
```
TIM2触发ADC采样 → DMA传输数据 → 缓冲区填满 → TIM6中断触发 → 计算极值
```
通过上述方法,可实现高效稳定的ADC极值检测,适用于传感器信号监测、电源监控等场景。
阅读全文
相关推荐

















