手把手教你搭建ROS阿克曼转向小车之(霍尔编码器数据读取与速度计算)

news2024/9/28 17:26:51

        上一篇文章已经介绍了如何驱动直流有刷电机转动起来,这篇文章讲解如何获取编码器的计数值,并且计算出速度信息。在实际的运行中,随着机器的重量不一样,电机受到的阻力就会不一样,给定同样的PWM在不同载重的情况下速度会不一样,要解决这个问题就需要引入反馈系统,使用PID进行调节,通过期望值和反馈值的信息动态的去调整PWM值,从而保证速度满足期望值的要求,而反馈值就是需要通过编码器来进行计算。

硬件介绍

        控制器的编码器端口如下图所示:

         其中PA0是定时器5的CH1、PA1是定时器5的CH2;PA15是定时器2的CH1、PB3是定时器2的CH2,定时器的编码器模式只能接在CH1和CH2端口上,STM32定时器的编码器模式原理需要大家自行去阅读手册,博文只讲解代码实现。

初始化代码

        初始化代码在HwConfig/hw_STM32F40x.c中:

void EncoderInit(uint8_t eName_t,uint8_t mMotorType){
	GPIO_TypeDef* ENCODER_A_PORT[ENCODERn] = {STARBOT_ENCODER1_A_GPIO_PORT, STARBOT_ENCODER2_A_GPIO_PORT, STARBOT_ENCODER3_A_GPIO_PORT, STARBOT_ENCODER4_A_GPIO_PORT};
	GPIO_TypeDef* ENCODER_B_PORT[ENCODERn] = {STARBOT_ENCODER1_B_GPIO_PORT, STARBOT_ENCODER2_B_GPIO_PORT, STARBOT_ENCODER3_B_GPIO_PORT, STARBOT_ENCODER4_B_GPIO_PORT};
	TIM_TypeDef*  ENCODER_TIM[ENCODERn] = {STARBOT_ENCODER1_TIM, STARBOT_ENCODER2_TIM, STARBOT_ENCODER3_TIM, STARBOT_ENCODER4_TIM,};
	const uint16_t  ENCODER_TIR[ENCODERn] = {STARBOT_ENCODER1_TIR,STARBOT_ENCODER2_TIR,STARBOT_ENCODER3_TIR,STARBOT_ENCODER4_TIR};
	const uint32_t  ENCODER_TIM_CLK[ENCODERn] = {STARBOT_ENCODER1_TIM_CLK, STARBOT_ENCODER2_TIM_CLK, STARBOT_ENCODER3_TIM_CLK, STARBOT_ENCODER4_TIM_CLK};
	const uint16_t  ENCODER_GPIO_AF_TIM[ENCODERn] = {STARBOT_ENCODER1_GPIO_AF_TIM,STARBOT_ENCODER2_GPIO_AF_TIM,STARBOT_ENCODER3_GPIO_AF_TIM,STARBOT_ENCODER4_GPIO_AF_TIM};
	uint32_t  ENCODER_A_PORT_CLK[ENCODERn] = {STARBOT_ENCODER1_A_GPIO_CLK, STARBOT_ENCODER2_A_GPIO_CLK, STARBOT_ENCODER3_A_GPIO_CLK, STARBOT_ENCODER4_A_GPIO_CLK,};
	uint32_t  ENCODER_B_PORT_CLK[ENCODERn] = {STARBOT_ENCODER1_B_GPIO_CLK,STARBOT_ENCODER2_B_GPIO_CLK,STARBOT_ENCODER3_B_GPIO_CLK,STARBOT_ENCODER4_B_GPIO_CLK};
	uint16_t  ENCODER_A_PIN[ENCODERn] = {STARBOT_ENCODER1_A_PIN, STARBOT_ENCODER2_A_PIN, STARBOT_ENCODER3_A_PIN, STARBOT_ENCODER4_A_PIN,};
	uint16_t  ENCODER_B_PIN[ENCODERn] = {STARBOT_ENCODER1_B_PIN, STARBOT_ENCODER2_B_PIN, STARBOT_ENCODER3_B_PIN, STARBOT_ENCODER4_B_PIN};
	uint16_t  ENCODER_A_GPIO_PinSource[ENCODERn] ={STARBOT_ENCODER1_A_GPIO_PinSource,STARBOT_ENCODER2_A_GPIO_PinSource,STARBOT_ENCODER3_A_GPIO_PinSource,STARBOT_ENCODER4_A_GPIO_PinSource};
	uint16_t  ENCODER_B_GPIO_PinSource[ENCODERn] ={STARBOT_ENCODER1_B_GPIO_PinSource,STARBOT_ENCODER2_B_GPIO_PinSource,STARBOT_ENCODER3_B_GPIO_PinSource,STARBOT_ENCODER4_B_GPIO_PinSource};

	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
	TIM_ICInitTypeDef TIM_ICInitStructure;  
	NVIC_InitTypeDef NVIC_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB1PeriphClockCmd(ENCODER_TIM_CLK[eName_t], ENABLE);  
	RCC_AHB1PeriphClockCmd(ENCODER_A_PORT_CLK[eName_t]|ENCODER_B_PORT_CLK[eName_t], ENABLE);

	GPIO_InitStructure.GPIO_Pin = ENCODER_A_PIN[eName_t];
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(ENCODER_A_PORT[eName_t], &GPIO_InitStructure);  

	GPIO_InitStructure.GPIO_Pin = ENCODER_B_PIN[eName_t]; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(ENCODER_B_PORT[eName_t], &GPIO_InitStructure);  

	GPIO_PinAFConfig(ENCODER_A_PORT[eName_t],ENCODER_A_GPIO_PinSource[eName_t],ENCODER_GPIO_AF_TIM[eName_t]);  
	GPIO_PinAFConfig(ENCODER_B_PORT[eName_t],ENCODER_B_GPIO_PinSource[eName_t],ENCODER_GPIO_AF_TIM[eName_t]);   

	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

	TIM_TimeBaseStructure.TIM_Prescaler = 0x0; 																					// No prescaling //不分频
	if(ENCODER1 == eName_t || ENCODER2 == eName_t){
		TIM_TimeBaseStructure.TIM_Period = 0xffffffff;  																		//设定计数器自动重装值
	} else {
		TIM_TimeBaseStructure.TIM_Period = 0xffff;  																			//设定计数器自动重装值
	}

	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 																	//选择时钟分频:不分频
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 																//TIM向上计数    
	TIM_TimeBaseInit(ENCODER_TIM[eName_t], &TIM_TimeBaseStructure);  															//初始化定时器
	TIM_EncoderInterfaceConfig(ENCODER_TIM[eName_t], TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);		//使用编码器模式3

	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_ICFilter = 6;
	TIM_ICInit(ENCODER_TIM[eName_t], &TIM_ICInitStructure);
	TIM_SetCounter(ENCODER_TIM[eName_t],0);
	TIM_ITConfig(ENCODER_TIM[eName_t], TIM_IT_Update, ENABLE);
	TIM_ClearITPendingBit(ENCODER_TIM[eName_t], TIM_IT_Update);															//清除TIM的更新标志位
	TIM_ClearFlag(ENCODER_TIM[eName_t], TIM_FLAG_Update);																//清除TIM的更新标志位
	TIM_Cmd(ENCODER_TIM[eName_t], ENABLE);

	NVIC_InitStructure.NVIC_IRQChannel=ENCODER_TIR[eName_t]; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; 
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

        定时器2和定时器5是32位定时器,所以设置计数为0xFFFFFFFF,然后我们还需要在溢出中断里对溢出次数进行处理,和需要一个定时器定时去计算单位时间内编码器的增量,然后换算为速度值。

溢出处理

        溢出处理的代码在APP/moveBase_Task.cpp中:

void TIM2_IRQHandler(void)
{ 		    		  			    
    if(TIM2->SR&0X0001)//溢出中断
	{ 
		if((TIM2->CR1 & 0x10) == 0x10){
			OverEnc2--;
		}else{
			OverEnc2++;
		}		
	}				   
	TIM2->SR&=~(1<<0);//清除中断标志位 	    
}
void TIM5_IRQHandler(void)
{ 		    		  			    
	if(TIM5->SR&0X0001)//溢出中断
	{    	
		if((TIM5->CR1 & 0x10) == 0x10){
		    OverEnc1--;
		}else{
			OverEnc1++;
		}			
	}				   
	TIM5->SR&=~(1<<0);//清除中断标志位 	    
}

        需要判断是向上溢出还是向下溢出,然后对溢出次数进行处理,处理完溢出后我们还需要一个定时器来计算他的单位时间内的增量,博主使用的是定时器6。

速度计算定时器初始化

        定时器6的初始化代码在HwConfig/hw_STM32F40x.c中:

void BaseBoard_TIM6_Init(void){	//10ms
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	//84MHz
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);  
	
	TIM_TimeBaseInitStructure.TIM_Period = 83; 	
	TIM_TimeBaseInitStructure.TIM_Prescaler=9999;  
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; 
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure);
	
	TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE); 
	TIM_Cmd(TIM6,ENABLE); 
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM6_DAC_IRQn; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; 
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

        这里给定定时器的周期是10ms,即100HZ,大家可以根据自己的需要进行修改,然后在定时器6的中断函数中对编码器数据进行处理。

编码器数据处理

        编码器数据处理的代码在APP/moveBase_Task.cpp中:

void TIM6_DAC_IRQHandler(void)
{   
	if(TIM_GetITStatus(TIM6,TIM_IT_Update)==SET) {
		encPIDCnt++;
		if(encPIDCnt >= DELTA_CNT){  // DELTA_CNT = 10
		    编码器采样周期 10 x 10ms = 100ms
		    if(MotorType_t != M_CAN_1_2){					
    		    gCurrentEnc1 = (int64_t)(((TIM5 -> CNT)) + OverEnc1*0xffffffff);
				gCurrentEnc2 = (int64_t)(((TIM2 -> CNT)) + OverEnc2*0xffffffff);
						
				if(lastCurrentEnc1 == 0){lastCurrentEnc1 = gCurrentEnc1;}
				delta_ticks_1 = gCurrentEnc1 - lastCurrentEnc1;
				lastCurrentEnc1 = gCurrentEnc1;
				if(lastCurrentEnc2 == 0){lastCurrentEnc2 = gCurrentEnc2;}
				delta_ticks_2 = gCurrentEnc2 - lastCurrentEnc2;
				lastCurrentEnc2 = gCurrentEnc2;
												
				Motor_Rpm.MotorEncoder1 += delta_ticks_1;
				Motor_Rpm.MotorEncoder2 += delta_ticks_2;
				Motor_Rpm.Current_Rpm1 = (delta_ticks_1*600/Time_counts_per_rev);
				Motor_Rpm.Current_Rpm2 = (delta_ticks_2*600/Time_counts_per_rev);
			}
		    moveBase_Manage();
		    encPIDCnt = 1;
		}
	}	
	TIM_ClearITPendingBit(TIM6,TIM_IT_Update);  				
}

        rpm的计算公式为:分钟内编码器的增量/输出轴转一圈编码器的计数值,我们这里是100ms计算一次,因此单位时间要进行转换:1min = 60s=60000ms 所以需要乘上600就可以得到RPM值。如果文章有错误欢迎大家及时纠正,感谢大家的支持

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

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

相关文章

Alien Skin ExposureX8最新ps中文版调色滤镜插件

Exposure是用于创意照片编辑的最佳图像编辑器。Exposure结合了专业级照片调整&#xff0c;庞大的华丽照片外观库以及高效的设计&#xff0c;使其使用起来很愉悦。新的自动调整功能可简化您的工作流程&#xff0c;并使您进入创意区。 Alien Skin Exposure 拥有超过500种预设效果…

基于线性支持向量机的词嵌入文本分类torch案例

一、前言 简介线性支持向量机,并使用线性支持向量机实现文本分类, 输入文本通过词嵌入方法转换成浮点张量,给出torch案例 线性支持向量机&#xff08;Linear Support Vector Machine&#xff0c;简称Linear SVM&#xff09;是一种常用的分类算法&#xff0c;它通过一个超平面来…

TiDB实战篇-TiDB Cluster部署

简介 部署TiDB Cluster部署&#xff0c;熟系集群的基础操作。 集群规划 机器拓扑 3pd,3tikv,1tidb_server.1tiflash,监控。 192.168.66.10192.168.66.20192.168.66.21 pd_servers tikv_servers tidb_servers tiflash_servers pd_servers tikv_servers monitoring_servers…

MySQL中使用IN()查询到底走不走索引?

MySQL中使用IN&#xff08;&#xff09;查询到底走不走索引&#xff1f; 看数据量 EXPLAIN SELECT * from users WHERE is_doctor in (0,1); 很明显没走索引&#xff0c;下面再看一个sql。 EXPLAIN SELECT * from users WHERE is_doctor in (2,1);又走索引了&#xff0c;所以…

Yolov5一些知识

1 Yolov5四种网络模型 Yolov5官方代码中&#xff0c;给出的目标检测网络中一共有4个版本&#xff0c;分别是Yolov5s、Yolov5m、Yolov5l、Yolov5x四个模型。 1.1Yolov5网络结构图 eg:Yolov5s 2.1 Yolov3&Yolov4网络结构图 2.1.1 Yolov3网络结构图 Yolov3的网络结构是…

Matlab论文插图绘制模板第86期—带置信区间的折线图

在之前的文章中&#xff0c;分享了很多Matlab折线图的绘制模板&#xff1a; 进一步&#xff0c;分享一种特殊的折线图&#xff1a;带置信区间的折线图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行…

【C++技能树】快速文本匹配 --正则表达式介绍与C++正则表达式使用

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法…感兴趣就关注我吧&#xff01;你定不会失望。 0.正则表达式存在必要性 在日常生活,或者刷题过程中我们难免需要检测一段字符是否需要是否符合规定,或在一大段字符中寻找自己想要的信息…

Mysql 数据库介绍

数据库介绍 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库&#xff0c;每个数据库都有一个或多个不同的API接口用于创建&#xff0c;访问&#xff0c;管理&#xff0c;搜索和复制所保存的数据。 我们也可以将数据存储在文件中&#xff0…

支持m2的主板换m2硬盘无法识别的问题,主板:七彩虹H410-T

记录一下我的电脑换m2硬盘遇到无法读取的问题&#xff0c;也给有同样问题的人留个参考&#xff0c;特别是七彩虹主板 主板&#xff1a;七彩虹H410-T 遇到的问题&#xff1a; m2 硬盘插上主板后&#xff0c;开机无法识别&#xff0c;打开我的电脑没有相应的盘&#xff0c;设备…

代码随想录---142. 环形链表 II

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整…

【C++】vector的实现

模拟实现vector类前言一、迭代器二、重载 [ ]三、构造函数相关&#xff08;重点&#xff09;&#xff08;1&#xff09;构造函数&#xff08;2&#xff09;构造并使用n个值为value的元素初始化&#xff08;3&#xff09;区间构造&#xff08;4&#xff09;拷贝构造三、析构函数…

什么是科学

人人都是价值观-思辨专家_个人渣记录仅为自己搜索用的博客-CSDN博客 相关文章 人人都是中医爱好者 科学定义 关于“科学”这个词的定义&#xff0c;历史上曾出现过多种版本&#xff0c;但是目前为止还没有一个是世人公认的定义。 历史上达尔文(Charles Robert Darwin&#xff…

利用阿里云免费部署openai的Chatgpt国内直接用

背景 国内无法直接访问ChatGPT&#xff0c;一访问就显示 code 1020。而且最近OpenAI查的比较严格&#xff0c;开始大规模对亚洲地区开始封号&#xff0c;对于经常乱跳IP的、同一个ip一堆账号的、之前淘宝机刷账号的&#xff0c;账号被封的可能性极大。 那么有没有符合openai规定…

< element-Ui表格组件:表格多选功能回显勾选时因分页问题,导致无法勾选回显的全部数据 >

文章目录&#x1f449; 前言&#x1f449; 一、解决思路&#x1f449; 二、实现代码&#xff08;仅供参考&#xff0c;具体问题具体分析&#xff09;> HTML模板> Js模板往期内容 &#x1f4a8;&#x1f449; 前言 在 Vue elementUi 开发中&#xff0c;elementUI中表格在…

Linux服务器怎么修改系统时间

Linux服务器怎么修改系统时间 linux服务器的系统时间&#xff0c;有的时候会产生误差&#xff0c;导致我们的程序出现一些延迟&#xff0c;或者其他的一些错误&#xff0c;那么怎么修改linux的系统时间呢&#xff1f; 我是艾西&#xff0c;今天又是跟linux小白分享小知识的时间…

C语言函数大全-- l 开头的函数

C语言函数大全 本篇介绍C语言函数大全-- l 开头的函数 1. labs&#xff0c;llabs 1.1 函数说明 函数声明函数功能long labs(long n);计算长整型的绝对值long long int llabs(long long int n);计算long long int 类型整数的绝对值 1.2 演示示例 #include <stdio.h> …

Python-Python基本用法(全:含基本语法、用户交互、流程控制、数据类型、函数、面向对象、读写文件、异常、断言等)

1 环境准备 编辑器&#xff1a;Welcome to Python.org 解释器&#xff1a;pycharm&#xff1a;Thank you for downloading PyCharm! (jetbrains.com) 2 Quick start 创建项目 new project create demo print(Dad!!)3 基本语法 3.1 print 直接打印 print(Dad!!)拼接打印…

记录-Vue.js模板编译过程揭秘:从模板字符串到渲染函数

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 Vue.js是一个基于组件化和响应式数据流的前端框架。当我们在Vue中编写模板代码时&#xff0c;它会被Vue编译器处理并转换为可被浏览器解析的JavaScript代码。Vue中的模板实际上是HTML标记和Vue指令的组…

STM32HAL库 串口USART的使用

STM32HAL库 串口USART的使用 文章目录STM32HAL库 串口USART的使用前言一、配置USART1串口通信引脚二、使用步骤三、串口中断回调函数1. 配置2. 在icode中增加usart.c和usart.h文件3. 中断处理对比4. 编写串口控制程序总结前言 本文为串口输出打印的hal库&#xff0c;参考洋桃电…

【LeetCode】剑指 Offer 57. 和为 s 的数字 p280 -- Java Version

1. 题目介绍&#xff08;57. 和为 s 的数字&#xff09; 面试题57&#xff1a;和为 s 的数字&#xff0c; 一共分为两小题&#xff1a; 题目一&#xff1a;和为 s 的两个数字题目二&#xff1a;和为 s 的连续正数序列 2. 题目1&#xff1a;和为s的两个数字 题目链接&#xff1…