前言:
声明:学习笔记来自正点原子B站教程,仅供学习交流!!
1. 采集原理
1.1 电流采集原理
- 电流采集实际上是通过对采样电阻进行电压采集,反推得到采样电阻的电流值,又因为采样电阻与电机回路在同一回路,所以经过采样电阻的电流值即电机电流值
- 采样电阻的电压经过差分放大进行放大,最终得到的输出电压为:
输出电压 = (6*0.02Ω*I)+ 1.27
- 采集输出电压计算实际电流I:
I = (旋转时电压 - 偏置电压)/0.12A
1.2 电压采集原理
- 电压采集电路首先通过分压,再将电压传给电压跟随器,电压为:
VBUS = POWER / (12 +12 + 1)
- 即电机电压为
POWER = VBUS*25
1.3 温度采集原理
- 热敏电阻通过分压传值到a处,再通过电压跟随器,即:
VTEMP = 4.7 * 3.3 / (4.7 + 热敏电阻Rt)
- 热敏电阻温度计算公式:
Rt = Rp *exp(B*(1/T1-1/T2))
注意: Rt 是热敏电阻在 T1 温度下的阻值; Rp 是热敏电阻在 T2 常温下的标称阻值;
exp 是 e 的 n 次方, e 是自然常数,就是自然对数的底数,近似等于 2.7182818; B 值是热敏电阻的重要参数,教程中用到的热敏电阻 B 值为 3380;
这里 T1 和 T2 指的是开尔文温度, T2 是常温 25℃,即(273.15+25)K
T1 就是所求的温度
2. 滤波算法
为了是曲线更加平滑,往往需要使用到滤波算法
参考:https://blog.csdn.net/qq_46336588/article/details/120272707
2.1 一阶互补滤波
方法:取a=0~1,本次滤波结果=(1-a)*本次采样值+a*上次滤波结果
优点:对周期性干扰具有良好的抑制作用适用于波动频率较高的场合
缺点:相位滞后,灵敏度低滞后程度取决于a值大小不能消除滤波频率高于采样频率的1/2的干扰信号*/
2.2 中位值滤波
方法:连续采样N次(N取奇数)把N次采样值按大小排列取中间值为本次有效值
优点:能有效克服因偶然因素引起的波动干扰;对温度、液位等变化缓慢的被测参数有良好的滤波效果
缺点:对流量,速度等快速变化的参数不宜*/
2.3 算术平均滤波
方法:连续取N个采样值进行算术平均运算;
N值较大时:信号平滑度较高,但灵敏度较低
N值较小时:信号平滑度较低,但灵敏度较高
N值的选取:一般流量,N=12;压力:N=4
优点:试用于对一般具有随机干扰的信号进行滤波。
这种信号的特点是有一个平均值,信号在某一数值范围附近上下波动。
缺点:测量速度较慢或要求数据计算较快的实时控制不适用。
2.4 递推平均滤波法
方法:把连续取N个采样值看成一个队列,队列的长度固定为N
每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出原则)
把队列中的N个数据进行算术平均运算,就可获得新的滤波结果
N值的选取:流量,N=12;压力:N=4;液面,N=4-12;温度,N=1~4
优点:对周期性干扰有良好的抑制作用,平滑度高;试用于高频振荡的系统
缺点:灵敏度低;对偶然出现的脉冲性干扰的抑制作用较差,不适于脉冲干扰较严重的场合
比较浪费RAM(改进方法,减去的不是队首的值,而是上一次得到的平均值)
2.5 中位值平均滤波
方法:相当于“中位值滤波法”+“算术平均滤波法”
连续采样N个数据,去掉一个最大值和一个最小值然后计算N-2个数据的算术平均值
N值的选取:3~14
优点:融合了两种滤波的优点。对于偶然出现的脉冲性干扰,可消除有其引起的
采样值偏差。对周期干扰有良好的抑制作用,平滑度高,适于高频振荡的系统。
缺点:测量速度慢。
2.6 递推中位值滤波法
优点:对于偶然出现的脉冲性干扰,可消除由其引起的采样值偏差。
对周期性干扰有良好的抑制作用,平滑度高;
试用于高频振荡的系统。
缺点:测量速度慢
取最近的10个值,去掉最大最小值求平均
队列queue中,第0个值换成新值,其余值依次往后移一个位置
2.7 限幅平均滤波法
方法:相当于“限幅滤波法”+“递推平均滤波法”
每次采样到的新数据先进行限幅处理再送入队列进行递推平均滤波处理
优点:对于偶然出现的脉冲性干扰,可消除有其引起的采样值偏差。
缺点:比较浪费RAM
2.8 加权递推平均滤波法
方法:是对递推平均滤波法的改进,即不同时刻的数据加以不同的权;
通常是,越接近现时刻的数值,权取得越大;
给予新采样值的权系数越大,则灵敏度越高,但信号平滑度越低。
优点:适用于有较大纯滞后时间常数的对象和采样周期较短的系统
缺点:对于纯滞后时间常数较小,采样周期较长,变化缓慢的信号不能迅速反应系统当前所受干扰的严重程度,滤波效果差
3. 软件程序
3.1 STM32CubeMX 配置
- ADC采集选择的是DMA传输
- 采集周期为480 Cycles,ADC时钟频率为84MHz,4分频,即循环一次周期 = (480 + 12)* 3 /(84 / 4)= 70.28us
3.2 DMA中断回调函数
- 将ADC采集到的数据经过 ADC_COLL 次累加后,取平均值,再依次计算温度,电压,电流
- 计算温度直接调用 get_motor_temp 函数得出结果
- 计算电压直接调用 get_motor_vol 函数得出结果
- 计算电流时稍微麻烦一点,因为电流跳动比较大,所以在得到平均值时,并未直接计算结果,而是存入到 curt_array 的数组里,待到数组元素满了之后,通过冒泡排序,去掉最大值和最小值,最后将剩余元素平均值传入到get_motor_curt得出结果
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
if(hadc->Instance == ADC1)
{
calc_motor_var(adc_raw_value);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_raw_value, 3);
}
}
void calc_motor_var(uint16_t adc[])
{
static uint32_t value[3] = {0};
static uint32_t adc_count = 0;
value[0] += adc[0];
value[1] += adc[1];
value[2] += adc[2];
adc_count++;
if(adc_count == ADC_COLL)
{
value[0] /= ADC_COLL;
value[1] /= ADC_COLL;
value[2] /= ADC_COLL;
dc_motor.temp = get_motor_temp(value[0]);
dc_motor.vol = get_motor_vol(value[2]);
{
static uint16_t curt_array[CURT_FILTER_COUNT] = {0};
static uint16_t array_number = 0;
curt_array[array_number++] = value[1];
if(array_number == CURT_FILTER_COUNT)
{
uint16_t temp = 0;
uint32_t sum = 0;
for(int i = 0; i < CURT_FILTER_COUNT; i++)
{
for(int j = 0; j < CURT_FILTER_COUNT - i - 1; j++ )
{
if(curt_array[j] > curt_array[j + 1])
{
temp = curt_array[j];
curt_array[j] = curt_array[j + 1];
curt_array[j + 1] = temp;
}
}
}
for(int i = 1; i < CURT_FILTER_COUNT - 1; i++)
{
sum += curt_array[i];
}
array_number = 0;
dc_motor.curt = get_motor_curt(sum/(CURT_FILTER_COUNT - 2));
}
}
adc_count = 0;
memset(value, 0, sizeof(value));
}
}
3.2.1 get_motor_temp
/**
* @brief 计算温度值
* @param para: 温度采集对应 ADC 通道的值(已滤波)
* @note 计算温度分为两步:
1.根据 ADC 采集到的值计算当前对应的 Rt
2.根据 Rt 计算对应的温度值
* @retval 温度值
*/
float get_motor_temp(uint32_t para)
{
float Rt;
float temp;
/*
第一步:
Rt = 3.3 * 4700 / VTEMP - 4700 ,其中 VTEMP 就是温度检测通道采集回来的电压
值,VTEMP = ADC 值* 3.3/4096
由此我们可以计算出当前 Rt 的值: Rt = 3.3f * 4700.0f / (para * 3.3f /
4096.0f ) - 4700.0f;
*/
Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f;
/*
第二步:
根据当前 Rt 的值来计算对应温度值: Rt = Rp *exp(B*(1/T1-1/T2))
*/
temp = Rt / Rp; /* 解出 exp(B*(1/T1-1/T2)) ,即 temp = exp(B*(1/T1-1/T2)) */
temp = log(temp); /* 解出 B*(1/T1-1/T2) ,即 temp = B*(1/T1-1/T2) */
temp /= Bx; /* 解出 1/T1-1/T2 ,即 temp = 1/T1-1/T2 */
temp += (1.0f / T2); /* 解出 1/T1 ,即 temp = 1/T1 */
temp = 1.0f / (temp); /* 解出 T1 ,即 temp = T1 */
temp -= Ka; /* 计算 T1 对应的摄氏度 */
return temp; /* 返回温度值 */
}
float get_motor_curt(uint32_t para)
{
static uint32_t curt_array[CURT_FILTER_COUNT] = {0};
static int curt_number = 0;
float sum = 0.0f;
if(curr_initial_value < 0.1)
{
curr_initial_value = para;
return 0.0f;
}
else
{
if(curt_number < CURT_FILTER_COUNT) curt_number++;
for(int i = CURT_FILTER_COUNT - 1; i > 0; i--)
{
curt_array[i] = curt_array[i - 1];
sum += curt_array[i - 1];
}
curt_array[0] = para;
sum += para;
if(sum < CURT_FILTER_COUNT) return abs((int)(para - curr_initial_value))*ADC2CURT;
}
return (abs((int)((sum/CURT_FILTER_COUNT) - curr_initial_value)))*ADC2CURT;
}
3.2.2 get_motor_vol
/* 电压计算公式:
* V_POWER = V_BUS * 25
* ADC 值转换为电压值:电压=ADC 值*3.3/4096
* 整合公式可以得出电压 V_POWER= ADC 值 *(3.3f * 25 / 4096)
*/
#define ADC2VBUS (float)(3.3f * 25 / 4096)
float get_motor_vol(uint32_t para)
{
return para*ADC2VBUS;
}
3.2.3 get_motor_curt
在get_motor_curt函数里主要做了两件事
- 在电机停止旋转时,将第一次得到的电压值赋值给偏置电压
- 在电机开始旋转时,将最近几次的电流采样值存放到数组中,每测得一个新的电流,就将新电流存入数组,将最早测得的电流值从数组中删除,我们使用的单流值是数组中所有速度的平均值(如果数组元素不足,即返回当前电流)
/* 电流计算公式:
* I=(最终输出电压-初始参考电压) /(6*0.02) A
* ADC 值转换为电压值:电压=ADC 值*3.3/4096,这里电压单位为 V,我们换算成
mV,4096/1000=4.096,后面就直接算出为 mA
* 整合公式可以得出电流 I= (当前 ADC 值-初始参考 ADC 值) * (3.3 / 4.096 / 0.12)
*/
#define ADC2CURT (float)(3.3f / 4.096f / 0.12f)
#define CURT_FILTER_COUNT 6 //电流过滤次数
float get_motor_curt(uint32_t para)
{
static uint32_t curt_array[CURT_FILTER_COUNT] = {0};
static int curt_number = 0;
float sum = 0.0f;
if(curr_initial_value < 0.1)
{
curr_initial_value = para;
return 0.0f;
}
else
{
if(curt_number < CURT_FILTER_COUNT) curt_number++;
for(int i = CURT_FILTER_COUNT - 1; i > 0; i--)
{
curt_array[i] = curt_array[i - 1];
sum += curt_array[i - 1];
}
curt_array[0] = para;
sum += para;
if(sum < CURT_FILTER_COUNT) return abs((int)(para - curr_initial_value))*ADC2CURT;
}
return (abs((int)((sum/CURT_FILTER_COUNT) - curr_initial_value)))*ADC2CURT;
}