3、ADC驱动配置:STM32/GD32的ADC多通道采样配置、采样时间与转换速率设置

好,咱们接着聊ADC的驱动配置。这部分内容,说白了就是让单片机里的ADC模块听话,按照咱们的要求去采集电流信号。我刚开始做逆变器项目时,觉得ADC配置不就是填几个寄存器嘛,结果一上电,采出来的波形全是毛刺,根本没法用。后来才发现,采样时间和通道切换顺序这些细节,一个没弄对,整个电流环就崩了。

3.1 多通道采样的两种模式

逆变器电流采样,至少需要两相电流(U相和V相),有时候还要加上母线电压和温度。这就涉及到多通道采样了。STM32和GD32的ADC,支持两种多通道模式:

  • 规则通道组(Regular Group):这是最常用的方式。你可以把多个通道排好队,ADC按顺序一个个采。我一般用这种方式,简单可靠。
  • 注入通道组(Injected Group):有点像中断,可以插队。平时不常用,除非你有特别紧急的采样需求。

我个人习惯用规则通道组的扫描模式(Scan Mode)配合连续转换(Continuous Conversion)。举个例子,我需要采集U相电流、V相电流和母线电压三个通道:

// 以STM32 HAL库为例,配置三个规则通道
ADC_ChannelConfTypeDef sConfig = {0};

// 配置U相电流通道(假设是ADC_IN0)
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;           // 第一个转换
sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

// 配置V相电流通道(ADC_IN1)
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;           // 第二个转换
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

// 配置母线电压通道(ADC_IN2)
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;           // 第三个转换
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

这里要注意,通道的排列顺序(Rank)决定了采样顺序。我在项目中遇到过一个问题:把U相和V相电流通道排在后面,母线电压排在前面。结果每次触发采样,先采的是电压,再采电流。虽然数据没错,但电流和电压的采样时刻差了几个微秒,导致功率计算有偏差。所以,关键信号(比如电流)一定要排在前面

3.2 采样时间与转换速率

采样时间,就是ADC给采样电容充电的时间。这个时间太短,电容充不满,采样值不准;时间太长,转换速率就慢了。你想想看,逆变器的电流环控制周期一般是几十微秒,ADC转换如果拖后腿,整个控制就滞后了。

STM32/GD32的ADC采样时间可以配置为多个档位:

采样周期数 适用场景 我的建议
1.5 Cycles 极低阻抗信号源 不推荐用于电流采样
7.5 Cycles 低阻抗信号源 如果运放驱动能力强可以用
15 Cycles 中等阻抗信号源 我常用的值,平衡了速度和精度
28.5 Cycles 高阻抗信号源 精度高,但速度慢
55.5 Cycles 极高阻抗或噪声大 除非万不得已,别用
我的经验:对于霍尔电流传感器或采样电阻+运放的方案,信号源阻抗通常在几十到几百欧姆。我一般选15个采样周期,既保证精度,又不拖慢速度。如果你发现采样值跳动厉害,可以试试28.5个周期,但要注意转换时间会变长。

转换速率怎么算?很简单:

总转换时间 = 采样时间 + 12.5个固定转换周期

比如ADC时钟是12MHz(周期约83.3ns),采样时间选15个周期:

总转换时间 = (15 + 12.5) × 83.3ns ≈ 2.29μs

三个通道就是 3 × 2.29μs ≈ 6.87μs。这个速度对于10kHz~20kHz的电流环来说,完全够用。

3.3 触发方式的选择

ADC什么时候开始转换?有三种常见方式:

  • 软件触发:调用HAL_ADC_Start(),想什么时候采就什么时候采。但CPU得一直等着,效率低。
  • 定时器触发:这是逆变器项目的标准做法。用PWM定时器的更新事件或触发输出,在PWM周期的特定时刻启动ADC。比如在PWM载波波谷或波峰触发,正好避开开关噪声。
  • 外部引脚触发:用外部信号触发,不常用。

我强烈建议用定时器触发。具体做法是:配置PWM定时器的TRGO(Trigger Output)事件,连接到ADC的触发输入。这样ADC转换和PWM开关动作是同步的,采样时刻非常精准。

// 配置定时器触发ADC(以TIM1为例)
// 使能TIM1的触发输出,选择更新事件作为触发源
TIM_MasterConfigTypeDef sMasterConfig = {0};
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);

// ADC配置中设置触发源为TIM1的TRGO
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
HAL_ADC_Init(&hadc1);
注意:我曾经犯过一个低级错误——把触发边沿设成了下降沿,结果ADC在PWM周期的错误时刻触发,采到的电流全是开关噪声。折腾了两天才发现是触发边沿的问题。所以,一定要确认触发时刻和PWM载波的关系

3.4 DMA与数据读取

多通道采样时,如果不用DMA,你得在中断里一个个读数据,CPU忙得团团转。我建议用DMA(直接存储器访问),让ADC自动把数据搬到内存数组里。

// 定义存放ADC数据的数组
uint32_t adc_buffer[3];  // 三个通道的数据

// 启动ADC的DMA传输
HAL_ADC_Start_DMA(&hadc1, adc_buffer, 3);

DMA传输完成后,会触发回调函数。你可以在回调里设置一个标志位,主循环检测到标志位后,直接读取数组里的数据。注意,DMA传输的数据顺序和Rank顺序一致:adc_buffer[0]对应Rank1(U相电流),adc_buffer[1]对应Rank2(V相电流),adc_buffer[2]对应Rank3(母线电压)。

避坑指南:我曾经在GD32F103上遇到过DMA传输数据错位的问题。原因是ADC的DMA请求和转换完成信号不同步。解决办法是:在启动DMA之前,先清空DMA的传输计数器,确保从第一个通道开始传输。代码里加一句 __HAL_DMA_DISABLE(&hdma_adc1); 再重新使能,问题就解决了。

3.5 实际配置步骤总结

嗯,说了这么多,咱们捋一下实际配置的步骤:

  1. 确定采样通道:U相电流、V相电流、母线电压(或温度),分配好ADC引脚。
  2. 配置ADC时钟:一般分频到12MHz~14MHz,别超过芯片手册的最大值。
  3. 设置采样时间:我推荐15个周期,如果噪声大可以增加到28.5个周期。
  4. 配置触发方式:用PWM定时器的TRGO触发,同步采样。
  5. 使能扫描模式和连续转换:让ADC自动循环采样所有通道。
  6. 配置DMA:把数据自动搬到内存数组,减少CPU负担。
  7. 启动ADC:调用HAL_ADC_Start_DMA(),开始干活。

最后提醒一句:采样时间不是越长越好。我见过有人为了追求精度,把采样时间设成55.5个周期,结果转换速率跟不上控制周期,电流环直接震荡。合适的才是最好的。