正点原子的电机例程(原例程用的是stm32f407,我这里改成用stm32f103zet6)
磁电增量式编码器介绍
编码器基本参数:
① 分辨率:编码器每个计数单位之间产生的距离,它是编码器可以测量到的最小的距离。对于增量式编码器,分辨率表示为编码器的转轴每旋转一圈所输出的脉冲数(PPR),也称为多少线,直流有刷电机教程中所使用的编码器是 11 线的。
② 精度:编码器分辨率和精度是两个独立的概念,精度是指编码器输出的信号数据与
实际位置之间的误差,常用角分′、角秒″表示。
③ 最大响应频率:编码器每秒能输出的最大脉冲数,单位 Hz,也称为 PPS。
④ 最大转速:指编码器机械系统所能承受的最高转速。
TIM定时器(编码器接口模式)
可以看到
当编码器正转时,A波形(高电平)在前,B波形在后
当编码器反转时,A波形(高电平)在后,B波形在前
根据下图判断计数方向与编码器信号的关系
下图有三种模式
1.仅在TI1计数(2倍频)
2.仅在TI2计数(2倍频)
3.在TI1和TI2上计数(4倍频)
注意:1、选择仅在 TI1 或者 TI2 处计数,就相当于对脉冲信号进行了 2 倍频(两个边沿),
此时如果编码器输出 10 个脉冲信号,那么就会计数 20 次。2、选择的是在 TI1 和 TI2 处均计
数,就相当于对脉冲信号进行了 4 倍频,此时如果编码器输出 10 个脉冲信号,那么就会计数
40 次。因此,我们通过计数次数来计算电机速度的时候,需要除以相应的倍频系数。
结合上图,可以看出:(此文章只研究1和2模式)
当编码器正转时,不论是在模式1或者模式2,计数值都是在增加
当编码器反转时,不论是在模式1或者模式2,计数值都是在减小
接下来我们就可以通过
一分钟内计数的变化量来计算电机的速度,具体公式如下:
电机转速 = 一分钟内计数变化量 / 倍频系数 / 编码器线数 / 减速比
这里我用的电机的参数如下:
一些用到的算法
均值滤波
例子:
十次平均值
uint long average_value()
{
for(i=0;i<=9;i++)
{
value += getvalue();
}
value /=10;
}
冒泡排序(从小到大)
例子:
if(k == 10)
{
for(i=10;i<=1;i–)
{
for(j=0;j<i-1;j++)
{
if(speed_arr[j]>speed_arr[j+1]) /* 数值比较
/
{
temp = speed_arr[j]; /
数值换位 */
speed_arr[j] = speed_arr[j+1] ;
speed_arr[j+1] = temp;
}
}
}
}
一阶低通滤波
- 公式为:Y(n)= qX(n) + (1-q)Y(n-1),其中
- X(n)为本次采样值;
- Y(n-1)为上次滤波输出值;
- Y(n)为本次滤波输值,
- q为滤波系数,
- q值越小则上一次输出对本次输出影响越大,整体曲线越平稳,但是对于速度变化的响应也会越慢
- 例子:
long Res_value;//先定义一个全局变量,用来存放结果
float Lv_Bo=00001;//定义一个滤波系数
long get_shuzhi()//返回一个64位的变量
{
float last_value;
float current_value;
last_value = Res_value;//记录上一次的值
current_value = getvalue();//获取当前值
if(current_value !=0)//读到正确值
{
Res_value = last_value*Lv_Bo + (1-Lv_Bo)*current_value;
}
return Res_value ;
}
结论:
q越大,响应越快,但曲线不平滑。
q越小,响应越慢,但曲线更平滑。
编码器测数代码
编码器接口HAL库函数
代码:
bsp_motor.c(测速代码)
/************************************* 第三部分 编码器测速 ****************************************************/
Motor_TypeDef g_motor_data; /*电机参数变量*/
ENCODE_TypeDef g_encode; /*编码器参数变量*/
/**
* @brief 电机速度计算
* @param encode_now:当前编码器总的计数值
* ms:计算速度的间隔,中断1ms进入一次,例如ms = 5即5ms计算一次速度
* @retval 无
*/
void speed_computer(int32_t encode_now, uint8_t ms)
{
uint8_t i = 0, j = 0;
float temp = 0.0;
static uint8_t sp_count = 0, k = 0; //ms(参数)ms计算一次,进行k要等于的次数的均值滤波(看231行),这个例程50ms计算一次,进行10次均值滤波
static float speed_arr[10] = {0.0}; /* 存储速度进行滤波运算 */
if (sp_count == ms) /* 计算一次速度 */
{
/* 计算电机转速
第一步 :计算ms毫秒内计数变化量
第二步 ;计算1min内计数变化量:g_encode.speed * ((1000 / ms) * 60 ,
第三步 :除以编码器旋转一圈的计数次数(倍频倍数 * 编码器分辨率)
第四步 :除以减速比即可得出电机转速
*/
g_encode.encode_now = encode_now; /* 取出编码器当前计数值 */
g_encode.speed = (g_encode.encode_now - g_encode.encode_old); /* 计算编码器计数值的变化量 */
speed_arr[k++] = (float)(g_encode.speed * ((1000 / ms) * 60.0) / REDUCTION_RATIO / ROTO_RATIO ); /* 保存电机转速 */
g_encode.encode_old = g_encode.encode_now; /* 保存当前编码器的值 */
/* 累计10次速度值,后续进行滤波*/
if (k == 10)
{
for (i = 10; i >= 1; i--) /* 冒泡排序*/
{
for (j = 0; j < (i - 1); j++)
{
if (speed_arr[j] > speed_arr[j + 1]) /* 数值比较 */
{
temp = speed_arr[j]; /* 数值换位 */
speed_arr[j] = speed_arr[j + 1];
speed_arr[j + 1] = temp;
}
}
}
temp = 0.0;
for (i = 2; i < 8; i++) /* 去除两边高低数据 */
{
temp += speed_arr[i]; /* 将中间数值累加 */
}
temp = (float)(temp / 6); /*求速度平均值*/
/* 一阶低通滤波
* 公式为:Y(n)= qX(n) + (1-q)Y(n-1)
* 其中X(n)为本次采样值;Y(n-1)为上次滤波输出值;Y(n)为本次滤波输出值,q为滤波系数
* q值越小则上一次输出对本次输出影响越大,整体曲线越平稳,但是对于速度变化的响应也会越慢
*/
g_motor_data.speed = (float)( ((float)0.48 * temp) + (g_motor_data.speed * (float)0.52) );
// g_motor_data.speed = temp;
k = 0;
}
sp_count = 0;
}
sp_count ++;
}
bsp_tim.h
/******************************** 3 公用部分 编码器程序 ************************************/
volatile int g_timx_encode_count = 0; /* 溢出次数 */
int Encode_now = 0;
/**
* @brief 定时器更新中断回调函数
* @param htim:定时器句柄指针
* @note 此函数会被定时器中断函数共同调用的
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM4)
{
if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim4)) /* 判断CR1的DIR位 */
{
g_timx_encode_count--; /* DIR位为1,也就是递减计数 */
}
else
{
g_timx_encode_count++; /* DIR位为0,也就是递增计数 */
}
}
else if (htim->Instance == TIM6)
{
Encode_now = gtim_get_encode(); /* 获取编码器值,用于计算速度 */
speed_computer(Encode_now, 50); /* 中位平均值滤除编码器抖动数据,50ms计算一次速度*/
}
}
/**
* @brief 获取编码器的值(当前编码器积累的值)
* @param 无
* @retval 编码器值
*/
int gtim_get_encode(void)
{
return ( int32_t )__HAL_TIM_GET_COUNTER(&htim4) + g_timx_encode_count * 65536; /* 当前计数值+之前累计编码器的值=总的编码器值 */
}
初始化的函数我就不放出来了,把我测速部分用到的HAL库的配置部分给大家看,
首先时钟树配置
定时器4来配置定时器编码器模式,用来计数编码器的脉冲数。
Encoder Mode 模式为Encoder Mode T1 and T12相当于4分频。
开启更新中断。
定时器6配置成基础定时器更新中断,中断时间为1ms.
计算公式:(999+1)*(71+1)/72M = 1ms
开启定时器6的中断
这里来介绍下此例程的流程:
首先初始化定时器4(编码器模式)和定时器6(基础更新中断),然后在初始化函数里开启定时器运行与开启中断。
每1ms发送一次中断(由定时器6产生),定时器4的中断回调函数里执行判断电机正转还是反转,正转就++,反转就–,50ms来计算一次电机的转速,然后
进行10次滤波,所以一共500ms来获取一次电机的转速值,又因为用了一阶低通滤波(因为电子产品本身就会存在误差,所以用这个算法)。