文山壮族苗族自治州网站建设_网站建设公司_SQL Server_seo优化
2026/1/16 8:16:55 网站建设 项目流程

零、前言

曾经在参加嵌入式竞赛时,我基于CH32V307制作了一个简易示波器。当时虽然实现了基本功能,但对其中定时器事件触发ADC和DMA搬运的理解只是停留在"照搬例程"的层面。最近重新学习了STM32的中断事件系统,才真正领悟到这种设计背后的精妙之处。今天我想和大家分享一下其具体实现,以及如何利用STM32的硬件特性实现高效的ADC采集。

一、中断/事件是什么?

在STM32的世界里,中断事件是两个既相似又不同的概念,理解它们的区别是高效系统设计的关键。

特性

中断

事件

触发与响应路径

软件主导:路径为外设 → NVIC → CPU → 软件ISR

硬件主导:路径为外设A → 硬件事件线 → 外设B

CPU参与度

全程参与。CPU必须暂停当前任务,执行中断服务程序。

无需参与。动作由硬件自动完成,CPU可休眠或执行其他任务。

延迟

相对较高。存在上下文保存/恢复等软件开销。

极低。硬件直接响应,无软件开销。

功耗控制

在频繁中断时,CPU频繁唤醒,功耗相对较高。

适合低功耗场景,CPU可保持睡眠,由事件触发特定动作后唤醒。

典型应用场景

处理复杂、非定期的任务,如数据包处理、错误响应、用户输入等。

处理实时性要求高、流程固定的协同操作,如ADC定时采集、定时器触发DMA等。

理解差异的关键在于明确它们的设计目的:

  • 中断的本质是通知CPU,当发生一个需要CPU来执行复杂逻辑处理的非预期事件时,就使用中断。它像是有人敲门,你必须停下手中的事情(保存上下文),去开门看看发生了什么(执行ISR),处理完后再回来继续工作。

  • 事件的本质是直接触发外设,它用于在多个硬件模块之间建立一条固定的“快速通道”,实现一个硬件动作自动触发另一个硬件动作。CPU在这个过程中可以被“蒙在鼓里”,从而专注于计算或进入节能状态。这像是设置了一个自动装置,当传感器检测到门开时,直接点亮电灯,无需你再手动去按开关。

简单来说,中断是通知CPU来处理,而事件是外设之间直接对话。在ADC采集中,这直接影响了系统的效率和实时性。

二、事件可以做什么?

事件机制是STM32外设间的"硬件高速公路",主要功能包括:

  1. 外设间直接通信:一个外设可以触发另一个外设的动作

  2. 硬件级同步:多个外设可以在硬件层面上精确同步

  3. CPU解脱:让CPU从频繁的中断响应中解放出来

  4. 低功耗设计:CPU可以在外设工作时进入睡眠模式

在简易示波器中,我们最关心的应用场景是:定时器事件触发ADC采样,DMA自动搬运数据

每个外设都有特定的事件输入事件输出端口,通过事件路由器连接。这种设计允许:

  1. 多个事件源:定时器、GPIO、外部引脚等都可作为事件源

  2. 多路复用:同一事件可触发多个外设

  3. 级联触发:事件可形成触发链

比如在示波器中常用的触发链是:

TIMx_Update → ADC1_Start → ADC1_Complete → DMA1_Transfer

三、定时器事件触发流程

下面以STM32F103为例,详细介绍如何配置定时器事件触发ADC与DMA搬运。

// ADC缓冲区和状态变量 #define ADC_BUFFER_SIZE 1024 volatile uint16_t adc_buffer[ADC_BUFFER_SIZE]; volatile uint8_t adc_data_ready = 0; volatile uint32_t adc_sample_count = 0; // 初始化函数 void Oscilloscope_Init(void) { // 1. 初始化定时器2 HAL_TIM_Base_Init(&htim2); // 2. 配置ADC DMA传输 HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE); // 3. 启动定时器(开始触发ADC) HAL_TIM_Base_Start(&htim2); // 4. 启用ADC HAL_ADC_Start(&hadc1); } // DMA传输完成中断回调 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if(hadc->Instance == ADC1) { adc_data_ready = 1; adc_sample_count += ADC_BUFFER_SIZE; // 通知主循环处理数据 // 这里可以设置标志或使用消息队列 } } // 数据处理部分 // 主循环中的数据处理 void ProcessWaveformData(void) { if(adc_data_ready) { // 1. 计算实际电压值 for(int i = 0; i < ADC_BUFFER_SIZE; i++) { float voltage = (adc_buffer[i] * 3.3f) / 4095.0f; // 2. 波形参数计算 CalculateWaveformParameters(adc_buffer, ADC_BUFFER_SIZE); // 3. 触发点检测 int trigger_index = FindTriggerPoint(adc_buffer, ADC_BUFFER_SIZE, trigger_level, trigger_edge); // 4. 数据存储或显示 StoreOrDisplayWaveform(adc_buffer, trigger_index); } adc_data_ready = 0; } } // 触发点检测函数 int FindTriggerPoint(uint16_t* buffer, uint32_t size, uint16_t threshold, uint8_t edge_type) { for(uint32_t i = 1; i < size; i++) { if(edge_type == RISING_EDGE) { if(buffer[i-1] < threshold && buffer[i] >= threshold) return i; } else if(edge_type == FALLING_EDGE) { if(buffer[i-1] > threshold && buffer[i] <= threshold) return i; } } return -1; // 未找到触发点 }

详细步骤:
1. TIM2计数器从0开始递增
2. 当计数器达到自动重装载值(999)时,产生更新事件
3. 更新事件通过TRGO输出到ADC的EXTI线
4. ADC检测到上升沿,开始转换
5. ADC转换完成后产生EOC(转换结束)事件
6. EOC事件触发DMA请求
7. DMA控制器从ADC数据寄存器读取转换结果
8. DMA将数据存储到内存缓冲区
9. 当DMA传输完成指定数量后,可产生DMA中断
10. CPU在DMA中断中处理数据

四、定时器事件触发的优点

1.精确的采样间隔

定时器事件触发提供了亚微秒级的精确采样间隔,这是示波器的基本要求。相比软件触发,定时器触发不受中断延迟、任务调度等不确定因素影响。

计算采样率
系统时钟 = 72MHz
定时器预分频 = 72-1 → 定时器时钟 = 1MHz
自动重载值 = 1000-1 → 更新频率 = 1KHz
实际采样率 = 1kSPS(每秒1000个采样点)

更高采样率配置:
要实现1MHz采样率:
预分频 = 72-1 → 定时器时钟 = 1MHz
自动重载值 = 1-1 → 更新频率 = 1MHz
注意:STM32F103的ADC最大采样率为1MHz

2.极低的CPU占用率

在传统的轮询或中断方式中,每个ADC采样都需要CPU参与。在高速采样时,这可能导致CPU完全被ADC采样占用。

CPU占用率对比分析,假设系统时钟72MHz,比较不同采样方式下的CPU占用:

// 1. 轮询方式 while(1) { HAL_ADC_Start(&hadc1); while(!HAL_ADC_PollForConversion(&hadc1, 10)); adc_value = HAL_ADC_GetValue(&hadc1); // 处理数据 } // 在1kSPS时,每个采样点消耗约1000个时钟周期 // CPU占用率 ≈ (1000 * 1000) / 72,000,000 ≈ 1.4% // 2. 中断方式 void ADC_IRQHandler(void) { adc_value = ADC1->DR; // 处理数据 } // 每个中断消耗约200个时钟周期(上下文切换+处理) // 在1kSPS时,CPU占用率 ≈ 0.3% // 3. 事件+DMA方式 // CPU只在DMA传输完成中断中处理数据 // 假设每次处理1024个点,每1024个点产生一次中断 // 在1MSPS时,每秒产生约1000次中断 // CPU占用率 ≈ (200 * 1000) / 72,000,000 ≈ 0.28%

采样方式

1kSPS

10kSPS

100kSPS

1MSPS

轮询

1.4%

14%

140%

不适用

中断

0.3%

3%

30%

300%

事件+DMA

0.003%

0.03%

0.3%

0.28%

3.确定性的系统行为

事件触发是纯硬件行为,具有完美的确定性。这对于示波器这样的实时测量设备至关重要。

确定性分析

  1. 时间抖动:软件触发存在微秒级抖动,硬件触发为纳秒级

  2. 优先级影响:中断方式受其他中断影响,事件方式完全独立

  3. 可预测性:硬件事件的时间可精确计算,软件触发不可预测

4.低功耗设计

由于CPU参与度低,大部分时间可以处于休眠状态,显著降低系统功耗。

// 进入低功耗模式示例 void Enter_LowPower_While_Sampling(void) { // 启动ADC和DMA HAL_ADC_Start_DMA(&hadc1, adc_buffer, BUFFER_SIZE); HAL_TIM_Base_Start(&htim2); // 配置DMA传输完成中断 __HAL_DMA_ENABLE_IT(&hdma_adc1, DMA_IT_TC); while(1) { // 等待DMA传输完成中断 __WFI(); // 进入睡眠模式 if(dma_transfer_complete) { // 处理数据 ProcessWaveformData(); dma_transfer_complete = 0; } } }

功耗对比测试(STM32F103 @ 72MHz):

工作模式

采样率

平均电流

节省比例

轮询模式

100kSPS

45mA

基准

中断模式

100kSPS

38mA

15.6%

事件+DMA+睡眠

100kSPS

12mA

73.3%

五、事件触发还有什么高级应用?

1. 电机控制(FOC)中的事件触发应用

磁场定向控制(FOC)​ 中,事件触发机制是关键。优势是确保电流采样在PWM周期中精确时间点进行,避免开关噪声干扰。通过定时器事件精确同步PWM生成、电流采样和数据计算,实现高效电机控制:

  • PWM定时器事件触发ADC同步采样三相电流

  • ADC完成转换后通过DMA自动传输到内存

  • 采样数据准备好后触发数学运算

  • 控制算法计算结果通过定时器事件更新PWM占空比

2 .数字电源控制

在开关电源中,事件触发实现高精度反馈控制:

  • ADC采样输出电压/电流 → 数字补偿器计算 → 事件触发更新PWM占空比

  • 硬件比较器检测过流 → 事件触发PWM紧急关断

  • 定时器事件同步多相交错PWM,减小输入电流纹波

3 .音频处理系统

专业音频设备利用事件触发实现高保真音频流:

  • I2S接口DMA事件自动传输音频数据

  • 定时器事件触发ADC/DAC实现精确采样率

  • 外部同步信号事件触发多设备音频同步

  • 音频处理完成事件触发DMA传输到输出接口

六、结尾

回顾我的简易示波器项目,当初只是机械地"参考例程"实现了功能,现在才真正理解了其中的精妙设计。STM32强大的事件系统让我们能够构建真正"硬件自动化"的系统,释放CPU的潜力,实现高效、实时、低功耗的设计。理解硬件,才能用好硬件。

这种设计模式不仅适用于示波器开发,还可广泛应用于各种需要高速数据采集的嵌入式系统,如振动监测、音频处理、医疗设备等领域。

希望这篇文章能帮助你更好地理解STM32的事件系统。如果你有任何问题、建议或自己的心得,欢迎在评论区交流讨论。

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

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

立即咨询