上节有这样一句话:
步进电机旋转角度和编码器输出数据之间的关系通常是非线性的。在校准过程中,可以通过采集一系列已知角度位置的数据点,并拟合出角度与编码器数据之间的关系。这个拟合可以使用曲线拟合算法或其他数学方法来实现。通过拟合,可以建立编码器输出数据与实际相位角之间的非线性转换公式,从而实现更准确的角度测量。
代码是这样实现的,在步进电机旋转的每一步都计算下一步和这一步的传感器读取数值的差,如果这个差值和预期不一致,则认为发生了阶跃,同时把阶跃差值确定。
uint32_t step_num = 0; //阶跃次数
if(encode_cali.dir){ //电机正转
for(count=0; count<200; count++){
sub_data = (int32_t)encode_cali.coder_data_f[CycleRem(count+1, 200)] - (int32_t)encode_cali.coder_data_f[CycleRem(count, 200)];
if(sub_data < 0){ //这个差值是应该大于0的,如果小于0则代表发生了阶跃,即转了超过1圈
step_num++; //阶跃次数加1
encode_cali.rcd_x = count;//使用区间前标
encode_cali.rcd_y = (2^14-1) - encode_cali.coder_data_f[CycleRem(encode_cali.rcd_x, 200)]; //阶跃差值
}
}
if(step_num != 1){
encode_cali.error_code = CALI_Error_PhaseStep; //如果阶跃次数不为1,则报错
return;
}
}
else{ //反转也是类似的,差值应该小于0,如果大于0则代表发生了阶跃
for(count=0; count<200; count++){
sub_data = (int32_t)encode_cali.coder_data_f[CycleRem(count+1, 200)] - (int32_t)encode_cali.coder_data_f[CycleRem(count, 200)];
if(sub_data > 0){
step_num++;
encode_cali.rcd_x = count;//使用区间前标
encode_cali.rcd_y = (2^14-1) - encode_cali.coder_data_f[CycleRem(encode_cali.rcd_x+1, 200)]; //这里要注意,反转需要把步数+1,即下一步的差值
}
}
if(step_num != 1){
encode_cali.error_code = CALI_Error_PhaseStep;
return;
}
}
到这里为止,整个编码器的校准过程就完成了。
接下去就是正常使用的过程。
typedef enum{
CALI_Disable = 0x00, //不校准
CALI_Forward_Encoder_AutoCali, //编码器正转自动校准
CALI_Forward_Measure, //正向测量
CALI_Reverse_Ret, //反向回退
CALI_Reverse_Gap, //反向消差
CALI_Reverse_Measure, //反向测量
CALI_Operation, //解算
}CALI_State;
会把它分为中断过程的回调使用和正常回调两个函数来执行。
switch(encode_cali.state) //看现时的编码器状态
{
case CALI_Disable:
if(encode_cali.trigger) //如果用户开启了校准请求,初始化也会使trigger置1
{
REIN_HW_Elec_SetDivideElec(encode_cali.out_location, Current_Cali_Current); //这一句是和FOC算法相关的,后面再学习
encode_cali.out_location = Move_Pulse_NUM; //步进电机转一圈,这个宏定义的值是单圈脉冲数,=200*256,256是单步细分数
encode_cali.gather_count = 0; //采集清零
encode_cali.state = CALI_Forward_Encoder_AutoCali; //--->编码器正转自动校准
encode_cali.error_code = CALI_No_Error; //初始化
encode_cali.error_data = 0;
}
break;
//编码器正转自动校准
case CALI_Forward_Encoder_AutoCali:
encode_cali.out_location += 2; //每次走2个脉冲,一直走到第二圈
REIN_HW_Elec_SetDivideElec(encode_cali.out_location, Current_Cali_Current);
if(encode_cali.out_location == 2 * Move_Pulse_NUM)
{
encode_cali.out_location = Move_Pulse_NUM; //重置到一圈
encode_cali.state = CALI_Forward_Measure; //走到第二圈后到下一个状态,正向测量
}
break;
//正向测量
case CALI_Forward_Measure:
if((encode_cali.out_location % Move_Divide_NUM) == 0)//每到达采集细分量点采集一次数据
{
//开始采集传感器的角度数据
encode_cali.coder_data_gather[encode_cali.gather_count++] = mt6816.angle_data;
if(encode_cali.gather_count == Gather_Quantity){
//记录数据
encode_cali.coder_data_f[(encode_cali.out_location - Move_Pulse_NUM) / Move_Divide_NUM]
= CycleDataAverage(encode_cali.coder_data_gather, Gather_Quantity, CALI_Encode_Res);
//采集计数清零
encode_cali.gather_count = 0;
//移动位置
encode_cali.out_location += 1; //每次移动1个脉冲
}
}
else{
//移动位置
encode_cali.out_location += 1;
}
REIN_HW_Elec_SetDivideElec(encode_cali.out_location, Current_Cali_Current);
if(encode_cali.out_location > (2 * Move_Pulse_NUM)) //如果走的脉冲数超过2圈了,进入下一个状态
{
encode_cali.state = CALI_Reverse_Ret;//--->反向回退
}
break;
//反向回退
case CALI_Reverse_Ret:
encode_cali.out_location += 1;
REIN_HW_Elec_SetDivideElec(encode_cali.out_location, Current_Cali_Current);
if(encode_cali.out_location == (2 * Move_Pulse_NUM + Move_Divide_NUM * 20)) //从第二圈再走20步,到下一个状态
{
encode_cali.state = CALI_Reverse_Gap;//--->反向消差
}
break;
//反向消差
case CALI_Reverse_Gap:
encode_cali.out_location -= 1; //每次往回退一个脉冲
REIN_HW_Elec_SetDivideElec(encode_cali.out_location, Current_Cali_Current);
if(encode_cali.out_location == (2 * Move_Pulse_NUM)) //退回到第二圈
{
encode_cali.state = CALI_Reverse_Measure;//--->反向测量
}
break;
//反向测量,和正向测量类似
case CALI_Reverse_Measure:
if((encode_cali.out_location % Move_Divide_NUM) == 0)//每到达采集细分量点采集一次数据
{
//采集
encode_cali.coder_data_gather[encode_cali.gather_count++] = mt6816.angle_data;
if(encode_cali.gather_count == Gather_Quantity){
//记录数据
encode_cali.coder_data_r[(encode_cali.out_location - Move_Pulse_NUM) / Move_Divide_NUM]
= CycleDataAverage(encode_cali.coder_data_gather, Gather_Quantity, CALI_Encode_Res);
//采集计数清零
encode_cali.gather_count = 0;
//移动位置
encode_cali.out_location -= 1; 回退一个脉冲
}
}
else{
//移动位置
encode_cali.out_location -= 1;
}
REIN_HW_Elec_SetDivideElec(encode_cali.out_location, Current_Cali_Current);
if(encode_cali.out_location < Move_Pulse_NUM)
{
encode_cali.state = CALI_Operation;//如果退回1圈以内,则进入下一个状态
}
break;
//计算
case CALI_Operation:
//进行校准计算中
REIN_HW_Elec_SetDivideElec(0, 0);
break;
default:
break;
}
}
可以看到,进中断时的操作是走一个流程,把每个状态都走一遍,并把采集的数据记录到coder_data_f
和coder_data_r
两个数组中。
下面看看主循环中的调用函数,下面代码中Move_Step_NUM
是步进电机总步数200,Move_Divide_NUM
是每步的细分数256,Move_Pulse_NUM是总的脉冲数,即256*200
void Calibration_Loop_Callback(void)
{
int32_t data_i32; //32位有符号
uint16_t data_u16; //16位无符号数
//必须要是校准计算状态才进入主循环,否则退出
if(encode_cali.state != CALI_Operation)
return;
//给电机的4线低电平
REIN_HW_Elec_SetSleep();
//传感器数据检查
Calibration_Data_Check();
if(encode_cali.error_code == CALI_No_Error)
{
int32_t step_x, step_y;
encode_cali.result_num = 0;
Stockpile_Flash_Data_Empty(&stockpile_quick_cali); //Flash擦除数据区
Stockpile_Flash_Data_Begin(&stockpile_quick_cali); //开始写数据区
if(encode_cali.dir){ //正转的情况
for(step_x = encode_cali.rcd_x; step_x < encode_cali.rcd_x + Move_Step_NUM + 1; step_x++) //从rcd_x的位置整一圈
{ //可以认为rcd_x是步数,rcd_y是该步数对应的传感器读数
data_i32 = CycleSub( encode_cali.coder_data_f[CycleRem(step_x+1, Move_Step_NUM)],
encode_cali.coder_data_f[CycleRem(step_x, Move_Step_NUM)],
CALI_Encode_Res); //data_i32的值存的是两步之间的传感器的读取数值差值
if(step_x == encode_cali.rcd_x){//开始边缘
for(step_y = encode_cali.rcd_y; step_y < data_i32; step_y++){
data_u16 = CycleRem( Move_Divide_NUM * step_x + Move_Divide_NUM * step_y / data_i32,
Move_Pulse_NUM); //data_u16存值:step_x这一步各个脉冲的传感器读取值
Stockpile_Flash_Data_Write_Data16(&stockpile_quick_cali, &data_u16, 1); //分为开始、中间、结尾3个区间获取并存入flash
encode_cali.result_num++;
}
}
else if(step_x == encode_cali.rcd_x + Move_Step_NUM){//结束边缘
for(step_y = 0; step_y < encode_cali.rcd_y; step_y++){
data_u16 = CycleRem( Move_Divide_NUM * step_x + Move_Divide_NUM * step_y / data_i32,
Move_Pulse_NUM);
Stockpile_Flash_Data_Write_Data16(&stockpile_quick_cali, &data_u16, 1);
encode_cali.result_num++;
}
}
else{//中间
for(step_y = 0; step_y < data_i32; step_y++){
data_u16 = CycleRem( Move_Divide_NUM * step_x + Move_Divide_NUM * step_y / data_i32,
Move_Pulse_NUM);
Stockpile_Flash_Data_Write_Data16(&stockpile_quick_cali, &data_u16, 1);
encode_cali.result_num++;
}
}
}
}
else //以下是反转的情况,同样也是把每一步的每个脉冲的传感器数值存入flash
{
for(step_x = encode_cali.rcd_x + Move_Step_NUM; step_x > encode_cali.rcd_x - 1; step_x--)
{
data_i32 = CycleSub( encode_cali.coder_data_f[CycleRem(step_x, Move_Step_NUM)],
encode_cali.coder_data_f[CycleRem(step_x+1, Move_Step_NUM)],
CALI_Encode_Res);
if(step_x == encode_cali.rcd_x+Move_Step_NUM){//开始边缘
for(step_y = encode_cali.rcd_y; step_y < data_i32; step_y++){
data_u16 = CycleRem( Move_Divide_NUM * (step_x+1) - Move_Divide_NUM * step_y / data_i32,
Move_Pulse_NUM);
Stockpile_Flash_Data_Write_Data16(&stockpile_quick_cali, &data_u16, 1);
encode_cali.result_num++;
}
}
else if(step_x == encode_cali.rcd_x){//结束边缘
for(step_y = 0; step_y < encode_cali.rcd_y; step_y++){
data_u16 = CycleRem( Move_Divide_NUM * (step_x+1) - Move_Divide_NUM * step_y / data_i32,
Move_Pulse_NUM);
Stockpile_Flash_Data_Write_Data16(&stockpile_quick_cali, &data_u16, 1);
encode_cali.result_num++;
}
}
else{//中间
for(step_y = 0; step_y < data_i32; step_y++){
data_u16 = CycleRem( Move_Divide_NUM * (step_x+1) - Move_Divide_NUM * step_y / data_i32,
Move_Pulse_NUM);
Stockpile_Flash_Data_Write_Data16(&stockpile_quick_cali, &data_u16, 1);
encode_cali.result_num++;
}
}
}
}
Stockpile_Flash_Data_End(&stockpile_quick_cali); //结束写数据区
if(encode_cali.result_num != CALI_Encode_Res) //result_num应该等于2^14
encode_cali.error_code = CALI_Error_Analysis_Quantity; //报解析数据错误
}
//确认校准结果
if(encode_cali.error_code == CALI_No_Error){
mt6816.rectify_valid = true; //磁编码器数据确认
}
else{
mt6816.rectify_valid = false; //不进行数据的存储
Stockpile_Flash_Data_Empty(&stockpile_quick_cali); //清除校准区数据
}
//运动配置覆盖
motor_control.stall_flag = true; //这是电机相关的,可以先不看,意思是堵转保护,即校准后禁用运动控制
//清理校准信号
encode_cali.state = CALI_Disable;
encode_cali.trigger = false; //清除校准触发
}
在实际使用时,当有外部事件要求(例如实体按键,或者用户界面按钮),会进入中断回调函数。
主loop程序会调用Calibration_Loop_Callback()
。
至此为止,磁编码器和电机驱动芯片的驱动基本完成了,接下去就是最核心的FOC算法实现对步进电机的控制。
未完待续