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

2023-11-04

        上一篇文章已经介绍了如何驱动直流有刷电机转动起来,这篇文章讲解如何获取编码器的计数值,并且计算出速度信息。在实际的运行中,随着机器的重量不一样,电机受到的阻力就会不一样,给定同样的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值。如果文章有错误欢迎大家及时纠正,感谢大家的支持

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • [人工智能AI]之贝叶斯网络

    人工智能AI 之贝叶斯网络 Bayesian network 部分图片和来源自 NJU 人工智能 高阳教授 的课件 通俗地讲 贝叶斯网络就是用一组有向无环图 表示多个事件的因果依赖关系 并借此完成相关推理计算 1 贝叶斯定理 条件概率 这里
  • linux服务器上部署多个vue项目(使用相同的ip(域名)及端口号)

    linux服务器上部署多个vue vue2 0 项目 使用相同的ip 域名 及端口号 1 首先想要实现的效果为 http ip 端口号 a 注 a代表a项目 http ip 端口号 b 注 b代表b项目 2 linux的文件路径为 3 此时

随机推荐

  • Qt迭代器(Java类型和STL类型)介绍

    一 介绍 遍历一个容器可以使用迭代器 iterators 来完成 迭代器提供了一个统一的方法来访问容器中的元素 而Qt的容器类提供了两种类型的迭代器 Java风格迭代器和STL风格迭代器 二 Java 类型迭代器 对于每个容器类 有两个 J
  • 【不定期更新-优化专题】

    文章目录 前言 一 学习资料类 前言 目前正在学习中 该专题主要用于分享自己的学习路线 工具 笔记等 欢迎交流与指正 一 学习资料类 视频 https www bilibili com video BV1Jt411p7jE 书籍下载 htt
  • VS:让程序运行完后不直接关闭

    Issue 控制台一闪而过 看不到输出结果 如何保持控制台窗口 让程序运行完后不直接关闭 Solve 1 推荐 建议设置项目属性 1 右键项目 gt gt 属性 Properties 打开项目的属性页 2 项目的属性页下 找到 配置属性 C
  • SQL语句优化问题

    有时返回数据时 需要有三张或以上的表需要查询 一张表的数据往往需要连接多张 效率非常的差 SELECT user id user uuid user user name AS username user login name name us
  • 测试项目:车牌检测,行人检测,红绿灯检测,人流检测,目标识别

    本项目为2020年中国软件杯 组第一批赛题 基于计算机视觉的交通场景智能应用 项目用python实现 主要使用YOLO模型实现道路目标如人 车 交通灯等物体的识别 使用开源的 中文车牌识别HyperLPR 项目实现车牌识别功能 github
  • OpenWRT UCI介绍及相关处理库

    OpenWRT UCI介绍及相关处理库 文章目录 OpenWRT UCI介绍及相关处理库 1 前言 2 概念 3 UCI配置文件 4 文件语法 5 命令行实用工具 5 1 用法 5 2 示例 导出整个配置 查看所有配置项的值 查看特定选项的
  • [Dotween] 介绍与使用,和坑

    Dotween 是在unity里实现各种动画效果 比如 位移 transform DoMove 旋转 transform DoRotation 缩放 transform DoScale 颜色改变 image DoColor 延时调用 Dov
  • QT中的QVariant类型-万能变量

    转自 https blog csdn net xiaopei yan article details 81410092 前言 QVariant这个类很神奇 或者说方便 很多时候 需要几种不同的数据类型需要传递 如果用结构体 又不大方便 容器
  • Vue前端自动化测试-Vue Test Utils

    Vue Test Utils简介 vue test utils是vue官方的单元测试框架 提供了一系列非常方便的工具 使我们更轻松地为vue构建的应用来编写单元测试 主流的JavaScript测试运行器有很多 但vue test utils
  • 中国人民大学和加拿大女王大学,学历的提升也是竞争力的提升

    如果想要读研提升自己在工作岗位上的竞争力和专业能力 报考在职研究生是一共非常不错的方式 在职研究生顾名思义就说国家计划内 以在职人员的身份 部分时间在职工作 部分时间在校学习的一种研究生类型 也是我国高等教育的重要组成部分 中国人民大学和加
  • 西洋经济史的趣味-赖建诚

    1990年代 台湾清华大学西洋经济史的老师 将的都是一些趣味性和严肃性的东西 明白一些经济学常识 1 经济学史到1993年 两个人获得诺贝尔经济学奖之后 才收到重视 2 这本书讲到了经济学史的重要性 3 火车轨道为什么是四尺八寸 因为这是国
  • web前端笔记

    web前端笔记 css选择器 标记选择器 如 div p 标签p id选择器 id class选择器 类名 通配符选择器 css文件外部链接 属性与属性之间用空格隔开 不是用逗号 HTML标签 双标签 p p 段落标签 标题标签 p h1
  • 微信开发 接口配置失败的坑新手注意

    申请的SAE 用来微信开发 填写完URL TOKEN后总会莫名其妙的爆出配置失败 如下的错误 出现这种原因有如下的原因 看你中招了没 1 最基本的检查这两个地方是否正确 怎么检查 不用我说吧 2 当发现上面的填写没问题 那就看这个你中招没
  • pandoc(markdown、latex、pdf、word相互转换的命令行工具)

    Markdown Pandoc 打通写作界的任督二脉 duqi yc的专栏 博客频道 CSDN NET http blog csdn net duqi yc article details 8974041 中文markdown转pdf Fl
  • java堆年轻代_Java堆(年轻代 -- 老年代 -- 永久代)

    JVM所管理的内存空间中 Java堆是最大的一块 主要用于存放各类实例对象 如下图 JVM中的堆被划分为两个不同区域 新生代Young 老年代Old 新生代又划分为Eden 伊甸 标志新生 Survivor0 s0 Survivor s1
  • 使用IDEA打包springcloud项目的jar包并发布至linux服务器

    前言 首先保证自己的springcloud项目在本地跑的通的 使用springcloud有可能缺少依赖的jar包 报 Java 程序包xxxx不存在 出现这种情况 因为配置Java的程序包这块出现了错误 同时可能你还没有设置让IDEA自动加
  • 【2023知乎爬虫】批量获取问题的全部回答

    一 需求 爬取任意问题下的所有回答 如下图 1 根据问题 批量获取问题下的所有回答 与对应问题的关系到answer csv文件 2 保存当前问题基本信息到quesiton info csv文件 二 展示爬取结果 三 讲解步骤 3 1 新建项
  • 《动手学深度学习 Pytorch版》 4.4 模型选择、欠拟合和过拟合

    4 4 1 训练误差和泛化误差 整节理论 详见书本 4 4 2 模型选择 整节理论 详见书本 4 4 3 欠拟合还是过拟合 整节理论 详见书本 4 4 4 多项回归 import math import numpy as np import
  • 表格嵌套表格数据

    表格嵌套表格数据 通常我们做联表查询 为了接收返回的数据我们往往需要书写一个类 这个类包含着接收两张表的数据字段 如下图 这是一种写法 但是如果面对一张表里的字段太多 那就得一个个去将这些字段写进类里 这种写法还可能让你的代码看上去比较繁杂
  • 手把手教你搭建ROS阿克曼转向小车之(霍尔编码器数据读取与速度计算)

    上一篇文章已经介绍了如何驱动直流有刷电机转动起来 这篇文章讲解如何获取编码器的计数值 并且计算出速度信息 在实际的运行中 随着机器的重量不一样 电机受到的阻力就会不一样 给定同样的PWM在不同载重的情况下速度会不一样 要解决这个问题就需要引