STM32自学笔记17-步进电机驱动项目-磁编码器的正常使用

上节有这样一句话:

步进电机旋转角度和编码器输出数据之间的关系通常是非线性的。在校准过程中,可以通过采集一系列已知角度位置的数据点,并拟合出角度与编码器数据之间的关系。这个拟合可以使用曲线拟合算法或其他数学方法来实现。通过拟合,可以建立编码器输出数据与实际相位角之间的非线性转换公式,从而实现更准确的角度测量。

代码是这样实现的,在步进电机旋转的每一步都计算下一步和这一步的传感器读取数值的差,如果这个差值和预期不一致,则认为发生了阶跃,同时把阶跃差值确定。

	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++;  //阶跃次数加1encode_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;}}

到这里为止,整个编码器的校准过程就完成了。

接下去就是正常使用的过程。

  1. 首先要定义一个编码器状态的枚举类型
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;

会把它分为中断过程的回调使用正常回调两个函数来执行。

  1. 首先是中断回调,一般是用户在正常工作时想要进行校准时的操作。来看下代码:
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_fcoder_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个区间获取并存入flashencode_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^14encode_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算法实现对步进电机的控制。

未完待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/7441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vscode使用g++编译.c文件或.cpp文件

vscode是一个跨平台、轻量级、插件非常丰厚的IDE&#xff0c;这里介绍在vscode里使用g来编译.cpp文件。g也叫GCC, 在Window中&#xff0c;是使用MinGW方式实现g的&#xff0c;它分为32位和64位2个版本&#xff0c;其中&#xff0c;MinGW-64是64位的&#xff0c;MinGW-32是32位的…

Hive解析JSON串

Hive 处理 json 数据总体来说有两个方向的路走&#xff1a; 将 json 以字符串的方式整个入 Hive 表&#xff0c;然后通过使用 UDF 函数解析已经导入到 hive 中的数据&#xff0c;比如使用 LATERAL VIEW json_tuple的方法&#xff0c;获取所需要的列名。 在导入之前将 json 拆成…

ConcurrentHashMap 相比于 HashMap 的优势

ConcurrentHashMap 使用每个链表头节点作为锁对象, 把一把大锁转换成多把小锁, 大大缩小了锁冲突的概率 HashTable 是给整个 Hash 表加锁, 因此只要有线程抢到了锁其他线程就得阻塞等待. ConcurrentHashMap 是对每个链表加锁, 因此只要不是对同一个链表进行修改就不会阻塞, 大…

【微信小程序】使用iView组件库的ActionSheet组件实现底部选择功能

效果1 效果2 要在微信小程序中使用iView组件库的ActionSheet组件&#xff0c;可以按照以下步骤进行&#xff1a; 首先&#xff0c;确保已经引入了iView组件库的样式和脚本文件。可以在app.wxss中引入iView的样式文件&#xff1a; import "/path/to/iview/weapp/dist/sty…

Ubuntu22.04部署K8s集群

Ubuntu22.04部署K8s集群 一、基础环境准备1.1 VMware Workstation Pro 17.01.2 Ubuntu22.04 二、系统环境配置2.1 设置Master与工作节点的机器名称及配置2.2 解析主机2.3 虚拟内存swap分区关闭2.4 开启IPv4转发2.5 设置时间同步2.6 开启防火墙的端口&#xff08;可选&#xff0…

linux下 UART串口相关

RS232的串口设备在linux 上会被识别为 /dev/ttyS* 或者 ttymxc* 一、串口简介 操作串口我们一般通过以下指令&#xff1a; 1、查看串口波特率等信息&#xff1a; stty -F /dev/ttyS0 -a #ttyS0为要查看的串口 2、设置串口参数&#xff1a; stty -F /dev/ttyS0 ispeed 115…

2023年java面试问题大全及答案大全

202年常见的Java面试问题和答案&#xff1a; Java中的基本数据类型有哪些&#xff1f; 答&#xff1a;Java中的基本数据类型包括整型(int, short, long, byte)、浮点型(float, double)、字符型(char)和布尔型(boolean)。 String和StringBuilder之间的区别是什么&#xff1f; 答…

微信小游戏个人开发者上架:从注册到上线的详细步骤

微信小游戏个人开发者上架&#xff1a;从注册到上线的详细步骤 一&#xff0c;注册小程序账号1.1 微信公众平台1.2 填写信息1.3 绑定管理 二&#xff0c;打包步骤2.1 工具准备2.2 关于Unity版本2.3 打包详解 三&#xff0c;提包步骤3.1 填写用户隐私3.2 完善开发者自查3.3 游戏…

5.string变量-读取一行

C里面的读一行的用法。getline&#xff08;cin,addr&#xff09;; 从标准输入设备cin&#xff0c;读取一行字符串保存到字符串变量addr中 如果用户直接回车什么都不读取就没有任何数据输入 读一行直到遇到回车符&#xff0c;注意不包括回车符。 判断字符串是不是空的 addr.em…

MYSQL的分类 DDL、DML、DQL、DCL

MySQL的分类 DDL、DML、DQL、DCL DDL(Data Definition Language&#xff0c;数据定义语言)DML(Data Manipulation Language&#xff0c;数据操纵语言)DQL(Data Query Language&#xff0c;数据查询语言)DCL(Data Control Language&#xff0c;数据控制语言) 1. DDL(Data Defi…

Cron 选择器

// 定义一个名为 cron 的新组件 Vue.component(cron, {name: cron,props: [data],data() {return {second: {cronEvery: ,incrementStart: 3,incrementIncrement: 5,rangeStart: ,rangeEnd: ,specificSpecific: [],},minute: {cronEvery: ,incrementStart: 3,incrementIncremen…

slam建图与定位_cartographer代码阅读(7)后端约束构建

1.cartographer里的节点:当扫描匹配结束后&#xff0c;有新的一帧scan加入到submap中&#xff0c;这个扫描匹配的结果就叫做节点 global_trajectory_builder.cc // 将匹配后的结果 当做节点 加入到位姿图中auto node_id pose_graph_->AddNode(matching_result->insertio…

2023年一建学霸笔记

考点:单方取消或辞去委托承担的民事责任女《民法典》规定&#xff0c;因解除合同造成对方损失的&#xff0c;除不可归责于该当事人的事由外&#xff0c;无偿委托合同的解除方应当赔偿因解除时间不当造成的直接损失&#xff0c;有偿委托合同的解除方应当赔偿对方的直接损失和合同…

简单理解TCP,UDP,HTTP

我们都知道TCP、UDP、HTTP内部有很复杂的过程&#xff0c;很多人没办法理解的那么深&#xff0c;只想知道这是个什么鬼。 1、TCP、UDP、HTTP 是什么? TCP/IP是个协议组&#xff0c;可分为三个层次&#xff1a;网络层、传输层和应用层。在网络层有IP协议、ICMP协议、ARP协议、…

关于云服务器ECS、宝塔的安装配置以及图床的使用

一、阿里云服务器的申请以及宝塔的安装 安装配置服务器的原理&#xff1a; step1&#xff1a;地址栏输入阿里云服务器官网地址 step2&#xff1a;在首页依次点击以下内容&#xff1a; step3&#xff1a;选择立即购买&#xff0c;并填写以下内容&#xff1a; step4&#xff1a…

Postman和Jmeter做接口测试的区别

1. 用例组织方式 Jmeter的组织方式相对比较扁平&#xff0c;它首先没有WorkSpace的概念&#xff0c;直接是TestPlan&#xff0c;TestPlan下创建的Threads Group就相当于TestCase&#xff0c;并没有TestSuite的层级。 Postman功能上更简单&#xff0c;组织方式也更轻量级&#…

[SQL挖掘机] - 子查询介绍

介绍: 子查询&#xff08;Subquery&#xff09;&#xff0c;也被称为嵌套查询或内部查询&#xff0c;是指在一个查询语句中嵌套使用的查询。它是将一个查询语句作为另一个查询语句的一部分来构建更复杂的查询逻辑。 子查询通常出现在主查询的条件、选择列表或 FROM 子句中&am…

opencv 之 外接多边形(矩形、圆、三角形、椭圆、多边形)使用详解

opencv 之 外接多边形&#xff08;矩形、圆、三角形、椭圆、多边形&#xff09;使用详解 本文主要讲述opencv中的外接多边形的使用&#xff1a; 多边形近似外接矩形、最小外接矩形最小外接圆外接三角形椭圆拟合凸包 将重点讲述最小外接矩形的使用 1. API介绍 #多边形近似 v…

Redisson实现简单消息队列:优雅解决缓存清理冲突

在项目中&#xff0c;缓存是提高应用性能和响应速度的关键手段之一。然而&#xff0c;当多个模块在短时间内发布工单并且需要清理同一个接口的缓存时&#xff0c;容易引发缓存清理冲突&#xff0c;导致缓存失效的问题。为了解决这一难题&#xff0c;我们采用Redisson的消息队列…

带你体验stable discussion文生图,实现自己的真人写真工具

前言 Midjourney 由于精致的画图风格备受好评&#xff0c;但由于其网络环境以及会员费&#xff0c;导致入门门槛过高&#xff0c;拦住了很多对AIGC感兴趣的小伙伴。今天带大家体验一下最近很火的Stable Diffusion&#xff0c;满足大家的AI爱好,无需科学上网&#xff0c;本地部…