基础知识补充
模式介绍:
- 扫描模式:使用ADC多通道时,自动使能扫描模式。自动扫描开启的所有通道进行转换,直到转换完成。
- 连续模式:开启连续模式后,ADC的转换不受其他控制(例如定时器触发),如果开启连续模式,ADC将忽略定时器的触发采样,连续转换模式其实就是满频率采样。
- 间断模式:可以将多个通道进行分组采样。假如开启了CH0、CH1、CH2、CH3这4个通道,设置间断次数为4,相当于将4个通道分成了4组,每组1个通道,要想采集完这4个通道就需要手动触发4次ADC采集。如果设置间断次数为2,那么采集完4个通道就需要手动触发2次ADC采集。
规则组使用的问题:
- 多通道+间断模式:如果不想使用DMA搬运数据,就使用间断模式。
- 多通道+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波形。
总结:
- PWM模式1:无论是向上计数还是向下计数,只要
CNT
CCRx
,PWM输出高电平。 - PWM模式2:无论是向上计数还是向下计数。只要
CNT
CCRx
,PWM输出低电平。
1.1 PWM模式1,向上计数,输出PWM波形
PWM模式1,向上计数,当CNT<CCR时,输出高电平。
1.2 PWM模式2,向上计数,输出PWM波形
PWM模式2,向上计数,当CNT<CCR时,输出低电平。
1.3 中心对齐模式,向上向下计数(三角波)
中央计数模式,最显著的特点:生成的PWM波形,是处于中心对称的!!!
1.6 计数中断事件
UEV:Update Event,更新事件。当计数器CNT=ARR-1,或者CNT=1时就自动生成更新事件。
- 中心对齐模式(
TIMx_RCR = 1
):计数器从0开始计数,到ARR-1产生一个计数器溢出事件,然后向下计数到1产生一个计数器溢出事件,然后再从0开始重新计数。
计数模式只是在输出PWM的时候有区别,对于普通的定时计数,无论是向上计数、向下计数,还是向上向下计数(中心对齐模式),进入中断的频率是一样的。计数器从0开始计数到ARR-1,计数到ARR-1触发中断,或CNT减为0触发中断。
2.定时器触发ADC规则组+多通道+DMA
注意事项:整个过程,不需要使能任何定时器中断,和ADC中断
- ADC连续转换模式:一定不要使能,使用单次转换。 因为我们的目标是使用定时器触发ADC转换,使用连续转换模式后,定时器触发一次ADC转换后,他自己接下来就自动开始转换了,此时定时器触发就不起作用了!
- DMA工作模式:选择循环模式,否则DMA搬完一次数据后,就不工作了,还需要再次使能DMA,使用这条语句触发DMA搬运数据
uint16_t adc2_value[2];
HAL_ADC_Start_DMA(&hadc2,(uint32_t *)adc2_value,2);
-
使能DMA连续请求
-
触发方式选择:例如上升沿,即为TIM输出PWM,会在PWM波的每个上升沿触发ADC转换。
-
定时器update事件:就是定时器计数溢出后,产生的触发信号。
2.1 STM32CubeMX配置
目标:使用定时器触发电位器和母线电压的ADC采样。
2.1.1 配置ADC2的工作参数:
1.配置ADC采样通道:
2.配置DMA搬运数据:
3.配置ADC的工作参数:
CPU主频160M,ADC预分配设为6分频(160M/6),采样时间设为12.5Cycles,注意采样时间如果设置的太小,可能会产生反复进入DMA中断,造成程序卡死的问题!!!
则ADC的转换时间 = (12.5+12.5)X6÷160M = 0.9375us,也就是ADC的转换频率为1.0667MHz。
4.配置触发条件:列出的有可选的触发条件。
选择TIM1的CH1比较事件,如果选择上升沿触发,那么配置定时器时要选择为
向上计数,PWM mode2
,才可以获得精准的ADC采样频率。如果选择下降沿触发,则配置定时器的工作模式为:向上计数,PWM mode1
。
注意也可以选择中心对齐模式
,不过要设置TIMx_RCR = 1
,原因可参考上一节的内容。
2.2.2 配置定时器的工作参数
1.配置PWM产生的通道:
2.配置TIM1的工作参数:配置为中心对齐模式,PWM频率10K
3.配置TIM1_CH1,PWM的工作参数:使用软件配置初始占空比,便于硬件调试最优的FOC低端电流采样位置。如果为其他信号的采集(比如母线电压,电位器),初始占空比只要不设置为100%或者0%都可以,否则无法产生PWM的上升沿和下降沿。
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中断
- ADC连续转换模式:一定不要使能,使用单次转换。 因为我们的目标是使用定时器触发ADC转换,使用连续转换模式后,定时器触发一次ADC转换后,他自己接下来就自动开始转换了,此时定时器触发就不起作用了!
- 其他内容可以参考“定时器触发ADC规则组的章节”
3.1 STM32CubeMX配置
与ADC触发规则组的不同之处:触发条件可选项目不同,这次选择TIM1_CH4通道产生PWM进行测试!
1.ADC工作参数配置:
2.禁用规则组:
3.配置注入组: 注意ADC采样时间如果设置的过小,程序可能会卡死无法正常工作!!!
4.配置TIM1的工作参数:
选择PWM模式2的原因:ADC采样触发选择的为上升沿,如果选择PWM模式1,则选择下降沿触发即可。
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。
/**
* @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");
}