基于ROS平台的移动机器人-2-小车底盘控制

【ROS基础】如何从零开始搭建一台ROS小车 references 基于STM32的ROS智能移动机器人构建地图、定位以及自主导航研发 从零搭建ROS机器人平台 ros app 下载、ros android app下载、ROS机器人控制app下载 阅读详情
AI摘要

本文介绍了基于ROS平台的移动机器人小车底盘控制方法,包括电机控制、PID控制及小车转弯控制等内容,并提供了详细的程序结构和实现代码。

基于ROS平台的移动机器人-2-小车底盘控制

说明

本博文将介绍小车底盘控制的原理,如PID控制,控制程序的编写等。

小车控制思想

  1. 控制电机转动。电机的控制我们分为两部分,一部分为电机转动方向的控制,另一个为电机转速的控制。电机转动的方向我们用两个MCU引脚来控制,假如PIN_A=1,PIN_B=0 时,电机正转;PIN_A=0,PIN_B=1 时,电机反转;PIN_A=0,PIN_B=0 时,电机停止。电机速度的控制则需要一个PWM输出引脚,我们通过控制输出不同的PWM值来控制电机转动的速度。
  2. PID控制。如果我们想控制小车以一米每秒的速度做直线,但由于地面的摩擦阻力的影响,会造成左右轮速度与我们想控制的速度不同,所以会走不直,这时我们就需要加入PID控制,PID控制的思想就是我实时的把轮子真正的速度采集回来和控制的速度对比,差则补,多则减。这样基本就可以实现理想控制。详细PID介绍查看。
  3. 小车转弯控制。一般我们要是想控制小车以多少的速度前进或者后退,我们只需要PID控制两个轮子的速度一致就可以基本做到。但如果要想控制小车以多少的角速度转弯,我们需要做一定的计算,如图所示:

推算过程这里就不算了,我们可以得到左右轮速度和线速度角速度之间的关系如下:

通过以上的公式我们就可以控制小车的任意行走了。

程序结构

以下为我的STM32工程主要文件

1.main.c 接收和发送串口数据,控制电机

/***********************************************  说明  *****************************************************************
*
*   1.串口接收
*    (1)内容:小车左右轮速度,单位:mm/s(所有数据都为   float型,float型占4字节)
*    (2)格式:10字节 [右轮速度4字节][左轮速度4字节][结束符"\r\n"2字节]
*
*   2.串口发送
*    (1)内容:里程计(x,y坐标、线速度、角速度和方向角,单位依次为:mm,mm,mm/s,rad/s,rad,所有数据都为float型,float型占4字节)
*    (2)格式:21字节 [x坐标4字节][y坐标4字节][方向角4字节][线速度4字节][角速度4字节][结束符"\n"1字节]
*
************************************************************************************************************************/
#include "stm32f10x.h"
#include "stm32f10x_it.h"

#include "delay.h"
#include "nvic_conf.h"
#include "rcc_conf.h"

#include "usart.h"
#include "encoder.h"
#include "contact.h"
#include "gpio_conf.h"
#include "tim2_5_6.h"
#include "odometry.h"
#include "tim2_5_6.h"

#include "stdbool.h"
#include <stdio.h>
#include "string.h"
#include "math.h"
/***********************************************  输出  *****************************************************************/

char odometry_data[21]={0};   //发送给串口的里程计数据数组

float odometry_right=0,odometry_left=0;//串口得到的左右轮速度

/***********************************************  输入  *****************************************************************/

extern float position_x,position_y,oriention,velocity_linear,velocity_angular;         //计算得到的里程计数值

extern u8 USART_RX_BUF[USART_REC_LEN];     //串口接收缓冲,最大USART_REC_LEN个字节.
extern u16 USART_RX_STA;                   //串口接收状态标记   

extern float Milemeter_L_Motor,Milemeter_R_Motor;     //dt时间内的左右轮速度,用于里程计计算

/***********************************************  变量  *****************************************************************/

u8 main_sta=0; //用作处理主函数各种if,去掉多余的flag(1打印里程计)(2调用计算里程计数据函数)(3串口接收成功)(4串口接收失败)

union recieveData  //接收到的数据
{
    float d;    //左右轮速度
    unsigned char data[4];
}leftdata,rightdata;       //接收的左右轮数据

union odometry  //里程计数据共用体
{
    float odoemtry_float;
    unsigned char odometry_char[4];
}x_data,y_data,theta_data,vel_linear,vel_angular;     //要发布的里程计数据,分别为:X,Y方向移动的距离,当前角度,线速度,角速度

/****************************************************************************************************************/  
int main(void)
{       
u8 t=0;
u8 i=0,j=0,m=0;

RCC_Configuration();      //系统时钟配置      
NVIC_Configuration();     //中断优先级配置
GPIO_Configuration();     //电机方向控制引脚配置
USART1_Config();          //串口1配置

TIM2_PWM_Init();          //PWM输出初始化
ENC_Init();               //电机处理初始化
TIM5_Configuration();     //速度计算定时器初始化
TIM1_Configuration();     //里程计发布定时器初始化

while (1)
{       
    if(main_sta&0x01)//执行发送里程计数据步骤
    {
        //里程计数据获取
        x_data.odoemtry_float=position_x;//单位mm
        y_data.odoemtry_float=position_y;//单位mm
        theta_data.odoemtry_float=oriention;//单位rad
        vel_linear.odoemtry_float=velocity_linear;//单位mm/s
        vel_angular.odoemtry_float=velocity_angular;//单位rad/s

        //将所有里程计数据存到要发送的数组
        for(j=0;j<4;j++)
        {
            odometry_data[j]=x_data.odometry_char[j];
            odometry_data[j+4]=y_data.odometry_char[j];
            odometry_data[j+8]=theta_data.odometry_char[j];
            odometry_data[j+12]=vel_linear.odometry_char[j];
            odometry_data[j+16]=vel_angular.odometry_char[j];
        }

        odometry_data[20]='\n';//添加结束符

        //发送数据要串口
        for(i=0;i<21;i++)
        {
            USART_ClearFlag(USART1,USART_FLAG_TC);  //在发送第一个数据前加此句,解决第一个数据不能正常发送的问题             
            USART_SendData(USART1,odometry_data[i]);//发送一个字节到串口 
            while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); //等待发送结束            
        }

        main_sta&=0xFE;//执行计算里程计数据步骤
    }
    if(main_sta&0x02)//执行计算里程计数据步骤
    {
        odometry(Milemeter_R_Motor,Milemeter_L_Motor);//计算里程计

        main_sta&=0xFD;//执行发送里程计数据步骤
    } 
    if(main_sta&0x08)        //当发送指令没有正确接收时
    {
        USART_ClearFlag(USART1,USART_FLAG_TC);  //在发送第一个数据前加此句,解决第一个数据不能正常发送的问题
        for(m=0;m<3;m++)
        {
            USART_SendData(USART1,0x00);    
            while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
        }       
        USART_SendData(USART1,'\n');    
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); 
        main_sta&=0xF7;
    }
    if(USART_RX_STA&0x8000)  // 串口1接收函数
    {           
        //接收左右轮速度
        for(t=0;t<4;t++)
        {
            rightdata.data[t]=USART_RX_BUF[t];
            leftdata.data[t]=USART_RX_BUF[t+4];
        }

        //储存左右轮速度
        odometry_right=rightdata.d;//单位mm/s
        odometry_left=leftdata.d;//单位mm/s

        USART_RX_STA=0;//清楚接收标志位
    }

    car_control(rightdata.d,leftdata.d);     //将接收到的左右轮速度赋给小车   
}//end_while
}//end main
/*********************************************END OF FILE**************************************************/

2.odometry.c 里程计计算

#include "odometry.h"

/***********************************************  输出  *****************************************************************/

float position_x=0,position_y=0,oriention=0,velocity_linear=0,velocity_angular=0;

/***********************************************  输入  *****************************************************************/

extern float odometry_right,odometry_left;//串口得到的左右轮速度

/***********************************************  变量  *****************************************************************/

float wheel_interval= 268.0859f;//    272.0f;        //  1.0146
//float wheel_interval=276.089f;    //轴距校正值=原轴距/0.987

float multiplier=4.0f;           //倍频数
float deceleration_ratio=90.0f;  //减速比
float wheel_diameter=100.0f;     //轮子直径,单位mm
float pi_1_2=1.570796f;          //π/2
float pi=3.141593f;              //π
float pi_3_2=4.712389f;          //π*3/2
float pi_2_1=6.283186f;          //π*2
float dt=0.005f;                 //采样时间间隔5ms
float line_number=4096.0f;       //码盘线数
float oriention_interval=0;  //dt时间内方向变化值

float sin_=0;        //角度计算值
float cos_=0;

float delta_distance=0,delta_oriention=0;   //采样时间间隔内运动的距离

float const_frame=0,const_angle=0,distance_sum=0,distance_diff=0;

float oriention_1=0;

u8 once=1;

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

//里程计计算函数
void odometry(float right,float left)
{   
    if(once)  //常数仅计算一次
    {
        const_frame=wheel_diameter*pi/(line_number*multiplier*deceleration_ratio);
        const_angle=const_frame/wheel_interval;
        once=0;
    }

    distance_sum = 0.5f*(right+left);//在很短的时间内,小车行驶的路程为两轮速度和
    distance_diff = right-left;//在很短的时间内,小车行驶的角度为两轮速度差

    //根据左右轮的方向,纠正短时间内,小车行驶的路程和角度量的正负
    if((odometry_right>0)&&(odometry_left>0))            //左右均正
    {
        delta_distance = distance_sum;
        delta_oriention = distance_diff;
    }
    else if((odometry_right<0)&&(odometry_left<0))       //左右均负
    {
        delta_distance = -distance_sum;
        delta_oriention = -distance_diff;
    }
    else if((odometry_right<0)&&(odometry_left>0))       //左正右负
    {
        delta_distance = -distance_diff;
        delta_oriention = -2.0f*distance_sum;       
    }
    else if((odometry_right>0)&&(odometry_left<0))       //左负右正
    {
        delta_distance = distance_diff;
        delta_oriention = 2.0f*distance_sum;
    }
    else
    {
        delta_distance=0;
        delta_oriention=0;
    }

    oriention_interval = delta_oriention * const_angle;//采样时间内走的角度  
    oriention = oriention + oriention_interval;//计算出里程计方向角
    oriention_1 = oriention + 0.5f * oriention_interval;//里程计方向角数据位数变化,用于三角函数计算

    sin_ = sin(oriention_1);//计算出采样时间内y坐标
    cos_ = cos(oriention_1);//计算出采样时间内x坐标

    position_x = position_x + delta_distance * cos_ * const_frame;//计算出里程计x坐标
    position_y = position_y + delta_distance * sin_ * const_frame;//计算出里程计y坐标

    velocity_linear = delta_distance*const_frame / dt;//计算出里程计线速度
    velocity_angular = oriention_interval / dt;//计算出里程计角速度

    //方向角角度纠正
    if(oriention > pi)
    {
        oriention -= pi_2_1;
    }
    else
    {
        if(oriention < -pi)
        {
            oriention += pi_2_1;
        }
    }
}

3.PID.c PID处理函数

#include "PID.h"

extern int span;

float MaxValue=9;//输出最大限幅
float MinValue=-9;//输出最小限幅

float OutputValue;//PID输出暂存变量,用于积分饱和抑制

float PID_calculate(struct PID *Control,float CurrentValue_left )//位置PID计算B
{

    float Value_Kp;//比例分量
    float Value_Ki;//积分分量
    float Value_Kd;//微分分量

    Control->error_0 = Control->OwenValue - CurrentValue_left + 0*span;//基波分量,Control->OwenValue为想要的速度,CurrentValue_left为电机真实速度
    Value_Kp = Control->Kp * Control->error_0 ;
    Control->Sum_error += Control->error_0;     

    /***********************积分饱和抑制********************************************/
    OutputValue = Control->OutputValue;
    if(OutputValue>5 || OutputValue<-5) 
    {
        Control->Ki = 0; 
    }
    /*******************************************************************/

    Value_Ki = Control->Ki * Control->Sum_error;
    Value_Kd = Control->Kd * ( Control->error_0 - Control->error_1);
    Control->error_1 = Control->error_0;//保存一次谐波
    Control->OutputValue = Value_Kp  + Value_Ki + Value_Kd;//输出值计算,注意加减

    //限幅
    if( Control->OutputValue > MaxValue)
        Control->OutputValue = MaxValue;
    if (Control->OutputValue < MinValue)
        Control->OutputValue = MinValue;

    return (Control->OutputValue) ;
}

4.encoder.c 电机编码处理

#include "encoder.h"

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

s32 hSpeed_Buffer2[SPEED_BUFFER_SIZE]={0}, hSpeed_Buffer1[SPEED_BUFFER_SIZE]={0};//左右轮速度缓存数组
static unsigned int hRot_Speed2;//电机A平均转速缓存
static unsigned int hRot_Speed1;//电机B平均转速缓存
unsigned int Speed2=0; //电机A平均转速 r/min,PID调节
unsigned int Speed1=0; //电机B平均转速 r/min,PID调节

static volatile u16 hEncoder_Timer_Overflow1;//电机B编码数采集 
static volatile u16 hEncoder_Timer_Overflow2;//电机A编码数采集

//float A_REMP_PLUS;//电机APID调节后的PWM值缓存
float pulse = 0;//电机A PID调节后的PWM值缓存
float pulse1 = 0;//电机B PID调节后的PWM值缓存

int span;//采集回来的左右轮速度差值

static bool bIs_First_Measurement2 = true;//电机A以清除速度缓存数组标志位
static bool bIs_First_Measurement1 = true;//电机B以清除速度缓存数组标志位

struct PID Control_left  ={0.01,0.1,0.75,0,0,0,0,0,0};//左轮PID参数,适于新电机4096
struct PID Control_right ={0.01,0.1,0.75,0,0,0,0,0,0};//右轮PID参数,适于新电机4096

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

s32 hPrevious_angle2, hPrevious_angle1;

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

void ENC_Init2(void)//电机A码盘采集定时器,TIM4编码器模式
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;    
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    TIM_DeInit(ENCODER2_TIMER);
    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

    TIM_TimeBaseStructure.TIM_Prescaler = 0;  
    TIM_TimeBaseStructure.TIM_Period = (4*ENCODER2_PPR)-1;

    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;   
    TIM_TimeBaseInit(ENCODER2_TIMER, &TIM_TimeBaseStructure);

    TIM_EncoderInterfaceConfig(ENCODER2_TIMER, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    TIM_ICStructInit(&TIM_ICInitStructure);
    TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;
    TIM_ICInit(ENCODER2_TIMER, &TIM_ICInitStructure);

    TIM_ClearFlag(ENCODER2_TIMER, TIM_FLAG_Update);
    TIM_ITConfig(ENCODER2_TIMER, TIM_IT_Update, ENABLE);

    TIM_Cmd(ENCODER2_TIMER, ENABLE); 
}

void ENC_Init1(void)//电机B码盘采集定时器,TIM3编码器模式
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;

    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);


    TIM_DeInit(ENCODER1_TIMER);
    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);

    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_Period = (4*ENCODER1_PPR)-1;  

    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;   
    TIM_TimeBaseInit(ENCODER1_TIMER, &TIM_TimeBaseStructure);

    TIM_EncoderInterfaceConfig(ENCODER1_TIMER, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    TIM_ICStructInit(&TIM_ICInitStructure);
    TIM_ICInitStructure.TIM_ICFilter = ICx_FILTER;
    TIM_ICInit(ENCODER1_TIMER, &TIM_ICInitStructure);

    TIM_ClearFlag(ENCODER1_TIMER, TIM_FLAG_Update);
    TIM_ITConfig(ENCODER1_TIMER, TIM_IT_Update, ENABLE);

    TIM_Cmd(ENCODER1_TIMER, ENABLE); 
}

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

s16 ENC_Calc_Rot_Speed2(void)//计算电机A的编码数
{   
    s32 wDelta_angle;
    u16 hEnc_Timer_Overflow_sample_one;
    u16 hCurrent_angle_sample_one;
    s32 temp;
    s16 haux;

    if (!bIs_First_Measurement2)//电机A以清除速度缓存数组
    {  
        hEnc_Timer_Overflow_sample_one = hEncoder_Timer_Overflow2;  
        hCurrent_angle_sample_one = ENCODER2_TIMER->CNT;
        hEncoder_Timer_Overflow2 = 0;
        haux = ENCODER2_TIMER->CNT;   

        if ( (ENCODER2_TIMER->CR1 & TIM_CounterMode_Down) == TIM_CounterMode_Down)  
        {
            // encoder timer down-counting 反转的速度计算     
            wDelta_angle = (s32)((hEnc_Timer_Overflow_sample_one) * (4*ENCODER2_PPR) -(hCurrent_angle_sample_one - hPrevious_angle2));
        }
        else  
        {
            //encoder timer up-counting 正转的速度计算
            wDelta_angle = (s32)(hCurrent_angle_sample_one - hPrevious_angle2 + (hEnc_Timer_Overflow_sample_one) * (4*ENCODER2_PPR));
        }       
        temp=wDelta_angle;
    } 
    else
    {
        bIs_First_Measurement2 = false;//电机A以清除速度缓存数组标志位
        temp = 0;
        hEncoder_Timer_Overflow2 = 0;
        haux = ENCODER2_TIMER->CNT;       
    }
    hPrevious_angle2 = haux;  
    return((s16) temp);
}


s16 ENC_Calc_Rot_Speed1(void)//计算电机B的编码数
{   
    s32 wDelta_angle;
    u16 hEnc_Timer_Overflow_sample_one;
    u16 hCurrent_angle_sample_one;
    s32 temp;
    s16 haux;

    if (!bIs_First_Measurement1)//电机B以清除速度缓存数组
    {   
        hEnc_Timer_Overflow_sample_one = hEncoder_Timer_Overflow1;  //得到采样时间内的编码数   
        hCurrent_angle_sample_one = ENCODER1_TIMER->CNT;
        hEncoder_Timer_Overflow1 = 0;//清除脉冲数累加
        haux = ENCODER1_TIMER->CNT;   

        if ( (ENCODER1_TIMER->CR1 & TIM_CounterMode_Down) == TIM_CounterMode_Down)  
        {
            // encoder timer down-counting 反转的速度计算
            wDelta_angle = (s32)((hEnc_Timer_Overflow_sample_one) * (4*ENCODER1_PPR) -(hCurrent_angle_sample_one - hPrevious_angle1));  
        }
        else  
        {
            //encoder timer up-counting 正转的速度计算
            wDelta_angle = (s32)(hCurrent_angle_sample_one - hPrevious_angle1 + (hEnc_Timer_Overflow_sample_one) * (4*ENCODER1_PPR));
        }
        temp=wDelta_angle;
    } 
    else
    {
        bIs_First_Measurement1 = false;//电机B以清除速度缓存数组标志位
        temp = 0;
        hEncoder_Timer_Overflow1 = 0;
        haux = ENCODER1_TIMER->CNT;       
    }
    hPrevious_angle1 = haux;  
    return((s16) temp);
}


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

void ENC_Clear_Speed_Buffer(void)//速度存储器清零
{   
    u32 i;

    //清除左右轮速度缓存数组
    for (i=0;i<SPEED_BUFFER_SIZE;i++)
    {
        hSpeed_Buffer2[i] = 0;
        hSpeed_Buffer1[i] = 0;
    }

    bIs_First_Measurement2 = true;//电机A以清除速度缓存数组标志位
    bIs_First_Measurement1 = true;//电机B以清除速度缓存数组标志位
}

void ENC_Calc_Average_Speed(void)//计算三次电机的平均编码数
{   
    u32 i;
    signed long long wtemp3=0;
    signed long long wtemp4=0;

    //累加缓存次数内的速度值
    for (i=0;i<SPEED_BUFFER_SIZE;i++)
    {
        wtemp4 += hSpeed_Buffer2[i];
        wtemp3 += hSpeed_Buffer1[i];
    }

    //取平均,平均脉冲数单位为 个/s  
    wtemp3 /= (SPEED_BUFFER_SIZE);
    wtemp4 /= (SPEED_BUFFER_SIZE); //平均脉冲数 个/s  

    //将平均脉冲数单位转为 r/min
    wtemp3 = (wtemp3 * SPEED_SAMPLING_FREQ)*60/(4*ENCODER1_PPR);
    wtemp4 = (wtemp4 * SPEED_SAMPLING_FREQ)*60/(4*ENCODER2_PPR); 

    hRot_Speed2= ((s16)(wtemp4));//平均转速 r/min
    hRot_Speed1= ((s16)(wtemp3));//平均转速 r/min
    Speed2=hRot_Speed2;//平均转速 r/min
    Speed1=hRot_Speed1;//平均转速 r/min
}

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

void Gain2(void)//设置电机A PID调节 PA2
{
    //static float pulse = 0;

    span=1*(Speed1-Speed2);//采集回来的左右轮速度差值
    pulse= pulse + PID_calculate(&Control_right,hRot_Speed2);//PID调节

    //pwm幅度抑制
    if(pulse > 3600) pulse = 3600;
    if(pulse < 0) pulse = 0;

    //A_REMP_PLUS=pulse;//电机APID调节后的PWM值缓存
}


void Gain1(void)//设置电机B PID调节 PA1
{
    //static float pulse1 = 0;

    span=1*(Speed2-Speed1);//采集回来的左右轮速度差值
    pulse1= pulse1 + PID_calculate(&Control_left,hRot_Speed1);//PID调节

    ////pwm 幅度抑制
    if(pulse1 > 3600) pulse1 = 3600;
    if(pulse1 < 0) pulse1 = 0;

    TIM2->CCR2 = pulse1;//电机B赋值PWM
    //TIM2->CCR3 = A_REMP_PLUS;//电机A赋值PWM
    TIM2->CCR3 = pulse;//电机A赋值PWM
}

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

void ENC_Init(void)//电机处理初始化
{
    ENC_Init2();              //设置电机A TIM4编码器模式PB6 PB7 右电机
    ENC_Init1();              //设置电机B TIM3编码器模式PA6 PA7 左电机
    ENC_Clear_Speed_Buffer();//速度存储器清零
}

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

void TIM4_IRQHandler (void)//执行TIM4(电机A编码器采集)计数中断
{   
    TIM_ClearFlag(ENCODER2_TIMER, TIM_FLAG_Update);
    if (hEncoder_Timer_Overflow2 != U16_MAX)//不超范围  
    {
        hEncoder_Timer_Overflow2++; //脉冲数累加
    }
}

void TIM3_IRQHandler (void)//执行TIM3(电机B编码器采集)计数中断
{  
    TIM_ClearFlag(ENCODER1_TIMER, TIM_FLAG_Update);
    if (hEncoder_Timer_Overflow1 != U16_MAX)//不超范围    
    {
        hEncoder_Timer_Overflow1++;  //脉冲数累加
    }
}

5.contact.c 电机控制函数

#include "contact.h"

/***********************************************  输出  *****************************************************************/

/***********************************************  输入  *****************************************************************/

extern struct PID Control_left;//左轮PID参数,适于新电机4096
extern struct PID Control_right;//右轮PID参数,适于新电机4096

/***********************************************  变量  *****************************************************************/

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

void LeftMovingSpeedW(unsigned int val)//左轮方向和速度控制函数
{     
    if(val>10000)
    {  
        GPIO_SetBits(GPIOC, GPIO_Pin_6);    
        GPIO_ResetBits(GPIOC, GPIO_Pin_7);  

        Control_left.OwenValue=(val-10000);//PID调节的目标编码数            
    }
    else if(val<10000)
    {  
        GPIO_SetBits(GPIOC, GPIO_Pin_7);    
        GPIO_ResetBits(GPIOC, GPIO_Pin_6);  

        Control_left.OwenValue=(10000-val);//PID调节的目标编码数     
    }   
    else
    {
         GPIO_SetBits(GPIOC, GPIO_Pin_6);   
         GPIO_SetBits(GPIOC, GPIO_Pin_7);

         Control_left.OwenValue=0;//PID调节的目标编码数
    }                   
}

void RightMovingSpeedW(unsigned int val2)//右轮方向和速度控制函数
{    
    if(val2>10000)
    {  
        /* motor A 正转*/
        GPIO_SetBits(GPIOC, GPIO_Pin_10);   
        GPIO_ResetBits(GPIOC, GPIO_Pin_11); 

        Control_right.OwenValue=(val2-10000);//PID调节的目标编码数
    }
    else if(val2<10000)
    {  
        /* motor A 反转*/
        GPIO_SetBits(GPIOC, GPIO_Pin_11);   
        GPIO_ResetBits(GPIOC, GPIO_Pin_10); 

        Control_right.OwenValue=(10000-val2);//PID调节的目标编码数   
    }   
    else
    {
        GPIO_SetBits(GPIOC, GPIO_Pin_10);   
        GPIO_SetBits(GPIOC, GPIO_Pin_11);

        Control_right.OwenValue=0;//PID调节的目标编码数
    }                                               
}

void car_control(float rightspeed,float leftspeed)//小车速度转化和控制函数
{
    float k2=17.179;         //速度转换比例,转/分钟  

    //将从串口接收到的速度转换成实际控制小车的速度?还是PWM?
    int right_speed=(int)k2*rightspeed;
    int left_speed=(int)k2*leftspeed;

    RightMovingSpeedW(right_speed+10000);
    LeftMovingSpeedW(left_speed+10000);
}

//void Contact_Init(void)//左右轮方向和速度初始化
//{
//  LeftMovingSpeedW(12000); //电机B
//  RightMovingSpeedW(12000);//电机A  
//}

完整工程代码下载

ros机器小车运动控制 通过对小车的使用可以知道,在 ROS 中运行程序大多是通过系统启动完成后,使用 ssh 软件或者虚拟机的 ssh 命令连接小车,再通过远程终端输入指令,运行相应的launch 文件才能执行相关程序。我们此次使用的小车使用的是树莓派ros程序,其中EPRobot 使用的路径规划和导航算法为 TEB 算法,该算法针对于阿克曼底盘结构的小车。定义X1方向为小车的正方向,则为机器人的方向角,因此用一个三维向量就完全可以表示机器人的运动路径,机器人在平面上的位姿表述。 阅读详情

相关推荐

制作ROS移动机器人地盘

制作ROS移动机器人地盘摘要概述硬件需求车体设计电路设计程序设计 摘要 本教程讲述如何利用扫地机轮子制作ROS移动机器人地盘。 概述 原本不打算自己造轮子的,但是网上的移动机器人地盘要么巨贵--对于学生党,要么巨简陋--编码器线数低于40线,根本不能满足ROS移动机器人的需要。为了锻炼自己的动手能力(这个借口看起来比较积极,能掩盖没钱的尴尬...),准备从零打造一款ROS移动机器人地盘。 扫地机轮组减速比大致是50左右,电机编码器15-20线,轮子转一周大概是1000个脉冲,测量精度够用。 通过L298N

SuperDevloper的博客 1727

开源自主导航小车MickX4(二)ROS底盘运动控制

开源自主导航小车MickX41 ROS底盘运动控制 1 ROS底盘运动控制 图展示了小车底盘运动控制的功能模块图 小车的运动控制包含遥控指令的解析,电机PID控制,电机反馈指令的解析 首先运行外设初始化函数 CAN模块初始化 串口初始化函数 IMU初始化函数 超声波模块初始化函数 PID初始化函数 小车核心代码1,串口1使用DMA接受,并解析大疆遥控器发送过来的指令 小车核心代码2,电机反馈数据 由于我们使用的大疆M3508电机反馈的数据中包含了电机的转角、速度、电流、温度信息,因此我们定义了一个如下的结构

熊猫飞天 2万+

【ROS2】Arduino与L298P电机驱动的机器人底盘DIY实战

本文详细介绍了如何利用ROS2、Arduino与L298P电机驱动模块DIY机器人底盘。内容涵盖硬件选型与电路连接、软件环境搭建、定制化固件开发、PID参数调试以及ROS2端集成配置,提供了一套从零开始构建稳定、可扩展移动机器人平台的完整实战方案,特别适合机器人爱好者和学生入门学习。

weixin_29194255的博客 443

开源一份基于STM32F1平台的ROS机器人底盘构建代码

前言 最初参考网上资料移植了一份基于串口的STM32通过rosserial和ROS系统通信的代码。对于小数据量格式的消息,可以正常通信,但消息量一大便会出错(如:发布ROS提供的odom和imu格式的消息)。网上还有另一种建立单片机和ROS系统的通信方式,https://blog.youkuaiyun.com/xiaoyuanwuhui/article/details/103203465这里的第二点有介绍,这...

ApexYuan 4351

搭建ROS小车底盘 | 构建自己的ROS小车平台

通过按照以上步骤,你可以搭建自己的ROS小车底盘。首先选择合适的底盘硬件,安装ROS并创建工作空间。接下来,编写ROS节点来控制底盘的移动和导航。我们需要编写ROS节点来控制底盘的移动和导航。根据底盘硬件的规格和接口,我们需要进行相应的配置。在本文中,我们将介绍如何构建自己的ROS小车平台,并提供相应的源代码供参考。接下来,我们需要将底盘的驱动程序添加到ROS工作空间的源代码目录中。现在,你的ROS小车底盘已经搭建完成。你可以通过发布相应的控制指令来测试底盘的移动和导航功能。是你的ROS包的名称,

loop_code966的博客 1759

ROS小车STM32底层控制代码

2.6 ROS小车STM32底层控制代码 经过之前几篇文章,我相信大家一定对下面这些模块都已经有些了解了。今天,我们将编写ROS小车STM32单片机底层控制的最后一篇文章。我们将一起按照之前的软件流程,将所有的代码整合到一个工程中去。 在公众号:小白学移动机器人,发送:ROS小车底层,即可获得源码工程文件。 相信大家对配置代码都很清楚了,这里直接描述软件执行流程,以及部分代码展示 注意:该工程文件中的STM32与ROS通信的程序和前面写的文章提供的程序,有一点出入,之前是为了方便理解。大家调用的时候注意即

「小白学移动机器人」一个专注分享移动机器人相关知识的公众号! 1万+

ROS使用STM32F407ZGT6作为底盘控制器

很久没有更新博客了,主要是最近这段时间在编写之前说自己做的STM32F407的底盘控制器的程序,现在已经编写完了,所以发个博客和大家分享一下,话不多说先上图。 PCB3D预览图, 看完图后我先介绍一下这个开发板具有的功能: 电源部分:6-18V输入,输出有5V,3.3V和5V4A可以直接给树莓派供电。 电源指示灯部分:5个,输入电源指示灯、5V电源指示灯、3.3V电源指示灯、RO...

qq_36349536的博客 6419

micro-ROS实战:将ROS 2下沉到MCU的移动机器人底盘控制

在移动机器人开发中,实时控制与高层决策往往分属两个世界:MCU负责电机闭环与IO处理,上位机运行ROS 2完成导航与感知,传统自定义串口协议让联调变得繁琐且脆弱。嵌入式实时系统与分布式通信架构的融合,正是为解决这一割裂而生的技术方向。micro-ROS通过XRCE协议在MCU上实现了ROS 2通信栈的精简移植,让传感器数据、控制指令以标准话题形式直达底层硬件。这种方案既保留了MCU的硬实时能力,又让嵌入式节点无缝接入ROS 2生态,大幅降低系统集成复杂度。该技术适用于差速底盘、服务机器人、教育科研等需要高实

weixin_29196315的博客 297

基于ROS平台的移动机器人-目录

基于ROS平台的移动机器人-目录说明本系列博文将介绍基于ROS平台的移动机器人的搭建,包括小车的底盘的制作,控制和与上位机的通信。目录 基于ROS平台的移动机器人-1-小车底盘的搭建 基于ROS平台的移动机器人-2-小车底盘控制 基于ROS平台的移动机器人-3-小车底盘与ROS的通信 基于ROS平台的移动机器人-4-通过ROS利用键盘控制小车移动 基于ROS平台的移动机器人-5-Kinect2驱动的

Forrest's Space 1万+

ros底盘控制.rar

ros底盘控制.rar

restbed编译笔记

Restbed 可以构建出基于asio库的restfulAPI服务,Restbed 4.7版本使用了1.12.0版本的asio(其他版本编译不通过),可直接访问github下载:https://github.com/chriskohlhoff/asio/tree/asio-1-12-0。另外Restbed还依赖openssl库(可直接下载exe的安装包,安装后取出lib库及头文件放到到restbed\dependency\openssl中即可,https://slproweb.com/download/W.

mahong100的博客 2106

ROS 移动机器人 STM32 Kinect2 智能车

基于ROS平台的移动机器人-目录 说明 本系列博文将介绍基于ROS平台的移动机器人的搭建,包括小车的底盘的制作,控制和与上位机的通信,建图和导航。 目录 基于ROS平台的移动机器人-1-小车底盘的搭建 基于ROS平台的移动机器人-2-小车底盘控制 基于ROS平台的移动机器人-3-小车底盘与ROS的通信 基于ROS平台的移动机器人-4-通过ROS利用键盘控制小车移动 基于ROS平台的...

uunubt的专栏 4009

基于FPGA的DDR学习——1.基础知识

⚪DDR2(Double Data Rate2)SDRAM是由JEDEC(电子设备工程联合委员会)开发的第三代SDRAM内存技术标准,1.8V工作电压,240线接口,提供了相较于DDR SDRAM更高的运行效能与更低的电压,同样采用在时钟的上升/下降沿同时进行数据传输的基本方式,但拥有两倍于上一代DDR内存预读取能力(即4bit数据预取能力),其常见的频率规范有DDR2 400\533\667\800\1066\1333等,总线频率553MHZ的DDR2内存只需133MHZ的工作频率。

qq_44932488的博客 2070

基于ROS平台的移动机器人-3-小车底盘与ROS的通信

基于ROS平台的移动机器人-3-小车底盘与ROS的通信1.ROS平台与底盘通信协议ROS平台与小车底盘的通信一般是通过串口或者CAN总线。我这里采用的是串口,以下为我自定义的通信数据格式:(1)底盘串口部分1.串口接收 (1)内容:小车左右轮速度,单位:mm/s(所有数据都为 float型,float型占4字节) (2)格式:10字节 [右轮速度4字节][左轮速度4字节][结束符"\r\n

Forrest's Space 1万+

基于ESP32-S3与模块化设计的立创桌面充电站:从硬件架构到Web控制实战

本文详细介绍了基于ESP32-S3与模块化设计的立创桌面充电站项目,涵盖从硬件架构到Web控制的全流程实战。项目采用模块化设计,支持自由组合快充模块,并通过ESP32-S3实现智能监控,用户可通过本地网页实时查看各接口电压、电流与功率数据。文章重点解析了基板、连接板与充电模块三大核心部件的设计,特别是大电流PCB的“开窗贴铜”处理技巧,并提供了固件刷写与Web控制面板的配置指南。

weixin_36122351的博客 293

[HCTF 2018]WarmUp 1学习记录

#[HCTF 2018]WarmUp 1学习笔记 最近做了做ctf,觉得真的很有趣,后悔当初没早点做,不过之前的学习也让我得到了不少经验基础,废话不多说,这次ctf虽然基本上靠了网上的大佬们(因为比较菜),但自己也做了些思考,应该对新手也比较友好吧。 ##1 首先怎么找源码,我承认我确实刚开始不知道在哪找,不过也很快找到了,相信也会有部分新手和我差不多吧 然后下面控制台会有个temp0,然后直接回车 后面我就受到启发,直接在链接后面加上source.php,进行页面跳转,然后就可以开始PHP代码审计了。

weixin_45869407的博客 684

基于ROS平台的移动机器人-4-通过ROS利用键盘控制小车移动

基于ROS平台的移动机器人-4-通过ROS利用键盘控制小车移动准备工作1.下载串口通信的ROS包(1)cd ~/catkin_ws/src (2)git@github.com:Forrest-Z/serial.git 2.下载键盘控制的ROS包(1)cd ~/catkin_ws/src (2)git@github.com:Forrest-Z/teleop_twist_keyboard.git 进入下

Forrest's Space 2万+

深入解析V4L2框架与高通KMD在Camera驱动中的协同设计

本文深入解析了V4L2框架与高通KMD在Camera驱动中的协同设计。V4L2作为Linux内核标准视频驱动框架,提供了统一的设备抽象与数据流管理;而高通KMD则在其基础上,通过引入CRM等核心模块,构建了一套针对移动平台复杂相机系统的高效内核态驱动架构,实现了硬件配置与执行的解耦与智能调度。

qq_27496129的博客 1147

移动机器人常见底盘及其运动学

通常移动机器人依赖电机驱动车轮实现行走功能。机器人底盘结构不同,其运动学也完全不同。根据不同类型车轮,常见的底盘结构差速运动模型、滑移运动模型、阿克曼运动模型、全向轮运动模型等等。

CG-SpatialAI 8661

突破Cloudflare 5秒盾:Python爬虫+GPT分析JS混淆,零基础也能爬

传统突破Cloudflare 5秒盾的门槛极高,需要精通JS逆向、浏览器指纹伪造、行为模拟,而本文的“Playwright+GPT”方案,彻底降低了门槛——无需懂逆向、无需手动改指纹、无需写复杂JS代码,只需3步就能突破大部分网站的防护。低门槛:新手1小时内就能上手,无需逆向工程知识;高成功率:覆盖80%+的普通防护网站,15%的中等防护网站;易扩展:适配不同类型网站,可结合代理、打码平台升级为高可用系统。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1163

GPT-5.5 是真好用!SCI论文润色与学术写作能力上全面提升,全流程指令已整理好!

输出规范:先输出完整的摘要正文,不添加额外说明,以Markdown表格形式提供中文自检说明,表格包含两列,列标题为“检查项”和“说明”,逐项说明是否已覆盖背景、研究问题、方法、结果及贡献,并简要评价摘要结构与表达是否符合要求,除上述内容外不输出其他信息。:你是一位(学术写作专家/期刊审稿人/论文评审专家),精通学术写作规范、逻辑结构、语法准确性及学术表达方式,你的任务是帮助提升论文的质量,包括语言润色、语法检查、逻辑优化和降低重复率。语言需简洁、正式、具有概括性,不展开具体论述,仅提供结构与要点说明。

智能AI让学术科研更容易 289

开源! ROS2阿克曼开发平台方案

这是一个完整的包含机械模型、硬件电路、底层驱动、上层应用等的ROS2 兼容差速和阿克曼的开发平台。

「小白学移动机器人」一个专注分享移动机器人相关知识的公众号! 6447

即插即用系列(代码实践) | WACV 2024 CSAM:面向各向异性医学图像分割的 2.5D 跨切片注意力模块

文章摘要: 论文《CSAM: A 2.5D Cross-Slice Attention Module for Anisotropic Volumetric Medical Image Segmentation》提出了一种解决医学图像各向异性问题的创新模块CSAM。针对MRI/CT数据中切片间分辨率差异大的挑战,该模块通过2.5D跨切片注意力机制,结合2D卷积的轻量性和3D上下文感知能力。核心包括语义注意力(通道级)、位置注意力(空间级)和跨切片注意力(支持不确定性建模),能在几乎不增加参数量的情况下有效捕捉

分享顶会顶刊论文即插即用模块 104

麦克纳姆轮小车底盘驱动资源介绍:ROS实验者的最佳助手

麦克纳姆轮小车底盘驱动资源介绍:ROS实验者的最佳助手 去发现同类优质开源项目:https://gitcode.com/ 在当代机器人开发领域,麦克纳姆轮小车因其灵活的移动特性而受到广泛关注。本项目为您带来的不仅仅是麦克纳姆轮小车的底盘驱动资源,更是一把打开ROS实验之门的钥匙。 项目介绍 本项目是一款专门为大疆A型版主控设计的麦克纳姆轮小车底盘驱动资源,旨在帮助ROS(Robot Operati...

gitblog_06783的博客 926

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

记录零基础ROS2学习和实践(三)(小车移动篇(一))(实物小车实现雷达建图和定点自动巡航)

上一章说了ros的功能包中一些文件的作用,这章我说说我是如何在ros2平台上让我的底盘小车实现移动。还有我使用的环境是untunbu22.04提前说明一下,让小车移动有很多方式,接下来我会介绍几种方式。

2301_79027493的博客 873

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

上一篇: PID控制算法
下一篇: 基于ROS平台的移动机器人-3-小车底盘与ROS的通信
Forrest-Z
博客等级 码龄11年 661粉丝 20原创
评论 16
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Forrest-Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值