STM32CubeMX学习笔记(6)--定时器触发ADC采样

4,623 阅读9分钟

基础知识补充

模式介绍:

  1. 扫描模式:使用ADC多通道时,自动使能扫描模式。自动扫描开启的所有通道进行转换,直到转换完成。
  2. 连续模式:开启连续模式后,ADC的转换不受其他控制(例如定时器触发),如果开启连续模式,ADC将忽略定时器的触发采样,连续转换模式其实就是满频率采样。
  3. 间断模式:可以将多个通道进行分组采样。假如开启了CH0、CH1、CH2、CH3这4个通道,设置间断次数为4,相当于将4个通道分成了4组,每组1个通道,要想采集完这4个通道就需要手动触发4次ADC采集。如果设置间断次数为2,那么采集完4个通道就需要手动触发2次ADC采集。

规则组使用的问题:

  1. 多通道+间断模式:如果不想使用DMA搬运数据,就使用间断模式。
  2. 多通道+DMA:使用DMA时,一定要将连续转换关闭! 如果连续模式开启,那么DMA传输到数组中时,每个通道所采集到的值对应在数组中的位置就不是固定的了!!!
    例如你开启了IN0~IN3这四个通道,并通过DMA将这四个通道的数据放到ADC_Value这个大小为4的u16类型数组,你在第一次采集的时候IN0通道的数值通过DMA被放在ADC_Value[0],第二次采集的时候IN0采集到的数值就可能被放到了ADC_Value[1],这样的话就极不方便我们对每个通道的数据进行分析和提取。
    参考文章:blog.csdn.net/qq153471503…

采样时间:
ADC转换时间 = 采样时间 + 12.5Cycles
得到的转换时间,为理论上ADC的最快转换时间。 ADC两次采样时间间隔,一定要大于ADC的转换时间。
例如设置ADC的转换时间为0.9375us,那么ADC的采样频率最小要大于1us,对应的采样频率要小于1M!

等待转换完成的函数:一定要使用,避免出BUG!

 HAL_ADC_PollForConversion(&hadc2, 50);           // 等待规则组转换完成, 最多等待时间50ms
 HAL_ADCEx_InjectedPollForConversion(&hadc1, 50); // 等待注入组转换完成

1.PWM mode1和PWM mode2的区别

TIM的 OC(Output Compare)输出比较 主要用于输出PWM波形,输出比较功能 可以通过比较 CNT计数器 与 CCR捕获/比较寄存器的大小,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。

image.png
总结:

  1. PWM模式1:无论是向上计数还是向下计数,只要CNT \leqslantCCRx,PWM输出高电平。
  2. PWM模式2:无论是向上计数还是向下计数。只要CNT \leqslantCCRx,PWM输出低电平。

1.1 PWM模式1,向上计数,输出PWM波形

PWM模式1,向上计数,当CNT<CCR时,输出高电平。

1.2 PWM模式2,向上计数,输出PWM波形

image.png

PWM模式2,向上计数,当CNT<CCR时,输出低电平。

1.3 中心对齐模式,向上向下计数(三角波)

中央计数模式,最显著的特点:生成的PWM波形,是处于中心对称的!!! image.png

1.6 计数中断事件

UEV:Update Event,更新事件。当计数器CNT=ARR-1,或者CNT=1时就自动生成更新事件。

  1. 中心对齐模式(TIMx_RCR = 1):计数器从0开始计数,到ARR-1产生一个计数器溢出事件,然后向下计数到1产生一个计数器溢出事件,然后再从0开始重新计数。

计数模式只是在输出PWM的时候有区别,对于普通的定时计数,无论是向上计数、向下计数,还是向上向下计数(中心对齐模式),进入中断的频率是一样的。计数器从0开始计数到ARR-1,计数到ARR-1触发中断,或CNT减为0触发中断。

image.png

2.定时器触发ADC规则组+多通道+DMA

注意事项:整个过程,不需要使能任何定时器中断,和ADC中断

  1. ADC连续转换模式:一定不要使能,使用单次转换。 因为我们的目标是使用定时器触发ADC转换,使用连续转换模式后,定时器触发一次ADC转换后,他自己接下来就自动开始转换了,此时定时器触发就不起作用了!
  2. DMA工作模式:选择循环模式,否则DMA搬完一次数据后,就不工作了,还需要再次使能DMA,使用这条语句触发DMA搬运数据
uint16_t adc2_value[2];
HAL_ADC_Start_DMA(&hadc2,(uint32_t *)adc2_value,2);
  1. 使能DMA连续请求

  2. 触发方式选择:例如上升沿,即为TIM输出PWM,会在PWM波的每个上升沿触发ADC转换。 image.png

  3. 定时器update事件:就是定时器计数溢出后,产生的触发信号。

2.1 STM32CubeMX配置

目标:使用定时器触发电位器和母线电压的ADC采样。
image.png

2.1.1 配置ADC2的工作参数:

1.配置ADC采样通道:
image.png

2.配置DMA搬运数据:
image.png

3.配置ADC的工作参数:
CPU主频160M,ADC预分配设为6分频(160M/6),采样时间设为12.5Cycles,注意采样时间如果设置的太小,可能会产生反复进入DMA中断,造成程序卡死的问题!!!
则ADC的转换时间 = (12.5+12.5)X6÷160M = 0.9375us,也就是ADC的转换频率为1.0667MHz。

image.png

4.配置触发条件:列出的有可选的触发条件。 image.png 选择TIM1的CH1比较事件,如果选择上升沿触发,那么配置定时器时要选择为向上计数,PWM mode2,才可以获得精准的ADC采样频率。如果选择下降沿触发,则配置定时器的工作模式为:向上计数,PWM mode1
注意也可以选择中心对齐模式,不过要设置TIMx_RCR = 1,原因可参考上一节的内容。
image.png

2.2.2 配置定时器的工作参数

1.配置PWM产生的通道:
image.png

2.配置TIM1的工作参数:配置为中心对齐模式,PWM频率10K
image.png

3.配置TIM1_CH1,PWM的工作参数:使用软件配置初始占空比,便于硬件调试最优的FOC低端电流采样位置。如果为其他信号的采集(比如母线电压,电位器),初始占空比只要不设置为100%或者0%都可以,否则无法产生PWM的上升沿和下降沿。
image.png

2.2 业务代码

#include "adc.h"
#include "Serial.h"
#include "stdint.h"
#include "tim.h"

uint16_t adc2_value[2];

void init_adc(void)
{
    TIM1->CCR1 = 8000 - 2;
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
    HAL_ADC_Start_DMA(&hadc2, (uint32_t *)adc2_value, 2);
}

void adc_log(void)
{
    log_block("%d,%d\r\n", adc2_value[0], adc2_value[1]);
}

已验证,可以正常使用!

3.定时器触发ADC注入组+多通道

注意事项:整个过程,不需要使能任何定时器中断,和ADC中断

  1. ADC连续转换模式:一定不要使能,使用单次转换。 因为我们的目标是使用定时器触发ADC转换,使用连续转换模式后,定时器触发一次ADC转换后,他自己接下来就自动开始转换了,此时定时器触发就不起作用了!
  2. 其他内容可以参考“定时器触发ADC规则组的章节”

3.1 STM32CubeMX配置

与ADC触发规则组的不同之处:触发条件可选项目不同,这次选择TIM1_CH4通道产生PWM进行测试! image.png

1.ADC工作参数配置: image.png

2.禁用规则组:
image.png

3.配置注入组: 注意ADC采样时间如果设置的过小,程序可能会卡死无法正常工作!!! image.png

4.配置TIM1的工作参数:
image.png

image.png

选择PWM模式2的原因:ADC采样触发选择的为上升沿,如果选择PWM模式1,则选择下降沿触发即可。
image.png

3.2 业务代码

#include "adc.h"
#include "Serial.h"
#include "stdint.h"
#include "tim.h"

uint16_t adc2_value[2];

void init_adc(void)
{
    TIM1->CCR4 = 8000 - 2;
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
    HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED); // ADC自校准
    HAL_ADCEx_InjectedStart(&hadc2);                       // 启动ADC注入组转换
}

void adc_log(void)
{
    adc2_value[0] = HAL_ADCEx_InjectedGetValue(&hadc2, ADC_INJECTED_RANK_1);
    adc2_value[1] = HAL_ADCEx_InjectedGetValue(&hadc2, ADC_INJECTED_RANK_2);
    log_block("%d,%d\r\n", adc2_value[0], adc2_value[1]);
}

已验证,可以正常工作!

BUG记录与解决

在FOC应用中,如果配置定时器触发ADC注入组采集三相低端电流时,在初始化ADC完成后,一定要延迟一会,因为注入注刚开始进行ADC转换时也需要时间,否则会出现偏置电压计算不正常的BUG。

image.png

/**
 * @brief 计算三相电流采样原始偏置值
 * @retval None
 */
void calculate_offset(void)
{
    uint16_t count = 1000;

    uint32_t ia_offset_sum, ib_offset_sum, ic_offset_sum;

    for (uint16_t i = 0; i < count; i++)
    {
        uint16_t ia_adc = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1); // ia
        uint16_t ib_adc = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_3); // ib
        uint16_t ic_adc = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_2); // ic

        ia_offset_sum += ia_adc;
        ib_offset_sum += ib_adc;
        ic_offset_sum += ic_adc;
    }
    ia_offset = ia_offset_sum / count;
    ib_offset = ib_offset_sum / count;
    ic_offset = ic_offset_sum / count;

    log_block("[ia_offset,ib_offset,ic_offset]:%d,%d,%d\r\n", ia_offset, ib_offset, ic_offset);
}

20231227最新解决方法

在获取ADC转换值前,先使用以下的函数等待ADC转换完成,之后再获取ADC转换值,进行计算!

 HAL_ADC_PollForConversion(&hadc2, 50);           // 等待规则组转换完成, 最多等待时间50ms
 HAL_ADCEx_InjectedPollForConversion(&hadc1, 50); // 等待注入组转换完成

等待注入组转换完成:

 // 等待注入组转换完成
    if (HAL_ADCEx_InjectedPollForConversion(&hadc1, 50) == HAL_OK)
    {
        // 获取ADC转换值
    }
    else
    {
        log_block("ADC Injected Conversion Error!\r\n");
    }

等待规则组转换完成:

// 等待规则组转换完成
    if (HAL_ADC_PollForConversion(&hadc2, 50) == HAL_OK)
    {
        // 获取ADC转换值
    }
    else
    {
        log_block("ADC Rejular Conversion Error!\r\n");
    }