[基于STM32底盘控制与ROS上层导航小车制作] 第一节 stm32电机驱动与编码器读取反馈

系列文章目录

第一节 stm32电机驱动与编码器读取反馈

第二节 stm32电机pid控制

第三节 stm32线速度标定

第四节 stm32添加mpu6050得到angle角度 

第五节 实现STM32与ubuntu系统下的ROS串口DMA通信,传输底盘速度等信息

第六节 ROS计算和发布里程计与tf变换

系列教材包含

底盘搭建与stm32代码编写,ros激光雷达建图与导航编写,实现动态避障与导航

底盘包含

两轮差速轮(自制)

四轮全向轮(所属团队大家一起搭建)

底盘不同就是在底盘的运动分解与控制,ros部分里程计计算和本地规划器不同,两轮用的base_local_Planner,四轮用的teb_local_planner,来规划发布速度控制底盘

代码个人github库gjhhust / Repositories · GitHubicon-default.png?t=L892https://github.com/gjhhust?tab=repositories


​​​​​​​


前言

第一步当然是驱动电机啦!

材料包含

L298N

两个带霍尔编码器电机


一、硬件初始化

1.pwm与电机正反转

我这里是PA8(TIM1 CH1),PA9((TIM1 CH2)输出pwm波控制,硬件接线自己定义吧,板子不一样肯定不一样,挑一个合适的修改我的代码即可

#include "pwm.h"

/*****************************电机控制*************************/

//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM1_PWM_Init(u16 arr,u16 psc)
{  
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO, ENABLE);// 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIO外设时钟使能
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);                                                                     	

   //设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//CH2 GPIO_Pin_9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//电机A口控制
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 |GPIO_Pin_12 ; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	GPIO_ResetBits(GPIOA,GPIO_Pin_11 |GPIO_Pin_12); //所有引脚拉低

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
	GPIO_ResetBits(GPIOB,GPIO_Pin_14 | GPIO_Pin_13); //引脚拉低
	
	
	
	TIM_TimeBaseStructure.TIM_Period = 899; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 80K
	TIM_TimeBaseStructure.TIM_Prescaler =0; //设置用来作为TIMx时钟频率除数的预分频值  不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
	TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	 
	
	
  TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主输出使能	

	TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
	
	TIM_Cmd(TIM1, ENABLE);  //使能TIM1
 


}


void motor_R_move(void)
{
	PBout(13) = 0;
	PBout(14) = 1;
}
void motor_R_back(void)
{
	PBout(13) = 1;
	PBout(14) = 0;
}

void motor_R_stop(void)
{
	PBout(13) = 0;
	PBout(14) = 0;
}
void motor_L_move(void)
{
	PAout(11) = 0;
	PAout(12) = 1;
}
void motor_L_back(void)
{
	PAout(11) = 1;
	PAout(12) = 0;
}

void motor_L_stop(void)
{
	PAout(11) = 0;
	PAout(12) = 0;	
}

代码解释

我这里除了初始化了pwm通道,还初始化了四个引脚作为L298N的逻辑控制,L298N是可以由两个引脚的0或者1能使输出颠倒从而实现电机正反转,具体谁1谁0自己试试就行,然后根据实际修改motor_R_move等四个函数控制电机的正反转。

至于PAout();就是使引脚输出的宏定义,你自己改一下只要能使得引脚输出高低电平即可。


2.编码器初始化

encoder.c

#include "encoder.h"
/**************************************************************************
函数功能:把TIM2初始化为编码器接口模式 右轮
入口参数:无
返回  值:无
**************************************************************************/
void Encoder_Init_TIM3(void)
{
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
        TIM_ICInitTypeDef TIM_ICInitStructure;  
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);    //使能定时器2的时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //使能PA端口时钟

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;        //端口配置
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   //浮空输入
        GPIO_Init(GPIOA, &GPIO_InitStructure);                                        //根据设定参数初始化GPIOA

        TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
        TIM_TimeBaseStructure.TIM_Prescaler = 0x0;              //预分频器 
        TIM_TimeBaseStructure.TIM_Period = 65535;  //设定计数器自动重装值
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM向上计数  
        TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
        
        TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
        
        TIM_ICStructInit(&TIM_ICInitStructure);
        TIM_ICInitStructure.TIM_ICFilter = 10;
        TIM_ICInit(TIM3, &TIM_ICInitStructure);
        TIM_ClearFlag(TIM3, TIM_FLAG_Update);                   //清除TIM的更新标志位
        TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
        //Reset counter
        TIM_SetCounter(TIM3,0);
        //===============================================
        TIM3->CNT = 0x7fff;
        //===============================================
        TIM_Cmd(TIM3, ENABLE); 
}
/**************************************************************************
函数功能:把TIM5初始化为编码器接口模式 左轮
入口参数:无
返回  值:无
**************************************************************************/
void Encoder_Init_TIM5(void)
{
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
        TIM_ICInitTypeDef TIM_ICInitStructure;  
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);    //使能定时器4的时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //使能PB端口时钟

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_0;        //端口配置
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   //浮空输入
        GPIO_Init(GPIOA, &GPIO_InitStructure);                                        //根据设定参数初始化GPIOA

        TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
        TIM_TimeBaseStructure.TIM_Prescaler = 0x0;              // 预分频器 
        TIM_TimeBaseStructure.TIM_Period = 65535;  //设定计数器自动重装值
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM向上计数  
        TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
	
        TIM_EncoderInterfaceConfig(TIM5, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
	
        TIM_ICStructInit(&TIM_ICInitStructure);
        TIM_ICInitStructure.TIM_ICFilter = 10;
        TIM_ICInit(TIM5, &TIM_ICInitStructure);
        TIM_ClearFlag(TIM5, TIM_FLAG_Update);                   //清除TIM的更新标志位
        TIM_ITConfig(TIM5, TIM_IT_Update, ENABLE);
        //Reset counter
        TIM_SetCounter(TIM5,0);
        //===============================================
        TIM5->CNT = 0x7fff;
        //===============================================
        TIM_Cmd(TIM5, ENABLE); 
}

代码解释

这里就是编码器初始化,每个霍尔电机编码器都需要AB两相来测速,我这里选择了TIM3与TIM5,至于AB接那个,先随便接,你只需安装好电机后到时候使电机正转,测速也是正数即可,反了的话换一下就行。


二、pwm控制与编码器使用

1.pwm使电机

很简单,只需要设定指定占空比,通俗来说当时初始化总的可以看成900,我们这里填0-900其比例就是电机转快慢

    TIM_SetCompare1(TIM1,500);//让tim1的CH1输出相应占空比pwm波
    TIM_SetCompare2(TIM1,500);//让tim1的CH2输出相应占空比pwm波

2.读入编码器速度

/**************************************************************************
函数功能:读取编码器脉冲差值
入口参数:TIM_TypeDef * TIMx
返回  值:无
**************************************************************************/
s16 getTIMx_DetaCnt(TIM_TypeDef * TIMx)
{
        s16 cnt;
        cnt = TIMx->CNT-0x7fff;     //这一点默认认为,单位时间内编码器不会出现0x7fff的脉冲变化,事实上也是这样
        TIMx->CNT = 0x7fff;
        return cnt;
}

/**************************************************************************
函数功能:计算左右轮速
入口参数:int *leftSpeed,int *rightSpeed
返回  值:无
                //计算左右车轮线速度,正向速度为正值 ,反向速度为负值
                //一定时间内的编码器变化值*转化率(转化为直线上的距离m)*200s(5ms计算一次) 得到 m/s *1000转化为int数据
								
								右1975.6 = 11 * 4 * 44.9
								左1975.6 = 11 * 4 * 44.9
                一圈的脉冲数:
                        左:  11
                        右:  11
                减速比
                        左: 44.9
                        右: 44.9
                轮子周长:13cm

**************************************************************************/

void Get_Motor_Speed(int *leftSpeed,int *rightSpeed)
{
        static int leftWheelEncoderNow    = 0;
        static int rightWheelEncoderNow   = 0;
        static int leftWheelEncoderLast   = 0;
        static int rightWheelEncoderLast  = 0;        
        
        //记录本次左右编码器数据
        leftWheelEncoderNow += getTIMx_DetaCnt(TIM5);
        rightWheelEncoderNow+= getTIMx_DetaCnt(TIM3);
                
        //5ms测速            
        *leftSpeed   =  13 * (leftWheelEncoderNow - leftWheelEncoderLast)*200 / 1975.6;  //速度为cm/s
        *rightSpeed  =  13 * (rightWheelEncoderNow - rightWheelEncoderLast)*200/ 1975.6;

        //记录上次编码器数据
        leftWheelEncoderLast  = leftWheelEncoderNow;                    
        rightWheelEncoderLast = rightWheelEncoderNow;                   
}


/**************************************************************************
函数功能:开启TIM6用以定时测速
入口参数:无
返回  值:无
**************************************************************************/
void TIM6_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); 

    TIM_TimeBaseInitStructure.TIM_Period = 50-1; 	
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;  
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
    TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStructure);
		TIM_ARRPreloadConfig(TIM6, ENABLE); //使能TIMx在ARR上的预装载寄存器

    

    NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; 
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
		
		TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
		TIM_Cmd(TIM6, ENABLE); 		
}

void TIM6_IRQHandler(void)   //TIM6中断
{
		if(TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
		{
			

			
			TIM_ClearITPendingBit(TIM6, TIM_IT_Update);   //清除TIMx的中断待处理位:TIM 中断源
					 
			Get_Motor_Speed(&F103RC_chassis.leftSpeedNow,&F103RC_chassis.rightSpeedNow);
			
	
}

代码解释

我这里设置了tim6为5ms中断,5ms进入中断调用函数Get_Motor_Speed测一次速度,测速公式也在代码注释给好了,原理可以自己百度查一下,这里是算出来的直接的,实际中摩擦等原因,我们可以在测速的结果乘一个系数来更符合实际,这里先提示一下。


总结

今天讲了基础的电机驱动与编码器测速

版权声明:本文为CSDN博主「LANLANLAN_hust」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/LanLanLan1927/article/details/119765860

LANLANLAN_hust

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

C程序在 Ubuntu 和 STM32 中内存分区

C程序在 Ubuntu 和 STM32 中内存分区 1、内存分区简介 程序在内存的分区 内存存放顺序 (由上到下) : 栈区 -> 堆区 -> 全局区 -> 常量区 -> 代码区 栈区(stack) 由编译器自动