ROS机器人底盘(21)-关于运动控制方向的补充

1.概述

使用PIBOT提供的小车已经完成了程序和硬件的调试,如果需要移植到自己的小车,可能会遇到PID调速不正常,解算得到运动结果不一致,反解得到里程有问题等,原因就是电机上的电机接线次序、编码器AB相次序,以及控制板输出口跟电机次序、,这里需要注意下面几点即可

  • a. 给定电机接口函数的输入参数正负与电机方向的关系
  • b.电机索引关系
  • c.电机转向得到编码器值正负关系

2.PIBOT运动解算和PID

void Robot::do_kinmatics(){
    if (!do_kinmatics_flag){
        for(int i=0;i<MOTOR_COUNT;i++){
            pid[i]->clear();
            encoder[i]->get_increment_count_for_dopid();
        }
        return;
    }
    
    static unsigned long last_millis=0;
    if (Board::get()->get_tick_count()-last_millis>=Data_holder::get()->parameter.do_pid_interval){
        last_millis = Board::get()->get_tick_count();
        
        for(int i=0;i<MOTOR_COUNT;i++){
            feedback[i] = encoder[i]->get_increment_count_for_dopid();
        }
#if PID_DEBUG_OUTPUT
    #if MOTOR_COUNT==2
        printf("input=%ld %ld feedback=%ld %ld\r\n", long(input[0]*1000), long(input[1]*1000), 
                                                        long(feedback[0]), long(feedback[1]));
    #endif
    #if MOTOR_COUNT==3
        printf("input=%ld %ld %ld feedback=%ld %ld %ld\r\n", long(input[0]*1000), long(input[1]*1000), long(input[2]*1000), 
                                                        long(feedback[0]), long(feedback[1]), long(feedback[2]));
    #endif
    #if MOTOR_COUNT==4
        printf("input=%ld %ld %ld %ld feedback=%ld %ld %ld %ld\r\n", long(input[0]*1000), long(input[1]*1000), long(input[2]*1000), long(input[3]*1000), 
                                                        long(feedback[0]), long(feedback[1]), long(feedback[2]), long(feedback[3]));
    #endif
#endif
        bool stoped=true;
        for(int i=0;i<MOTOR_COUNT;i++){
            if (input[i] != 0 || feedback[i] != 0){
                stoped = false;
                break;
            }
        }

        short output[MOTOR_COUNT]={0};
        if (stoped){
            for(int i=0;i<MOTOR_COUNT;i++){
                output[i] = 0;
            }
            do_kinmatics_flag = false;
        }else{
            for(int i=0;i<MOTOR_COUNT;i++){
                output[i] = pid[i]->compute(Data_holder::get()->parameter.do_pid_interval*0.001);
            }
        }

        for(int i=0;i<MOTOR_COUNT;i++){
            Data_holder::get()->pid_data.input[i] = int(input[i]);
            Data_holder::get()->pid_data.output[i] =  int(feedback[i]);
        }

#if PID_DEBUG_OUTPUT
    #if MOTOR_COUNT==2
        printf("output=%ld %ld\r\n\r\n", output[0], output[1]);
    #endif
    #if MOTOR_COUNT==3
        printf("output=%ld %ld %ld\r\n\r\n", output[0], output[1], output[2]);
    #endif
    #if MOTOR_COUNT==4
        printf("output=%ld %ld %ld %ld\r\n\r\n", output[0], output[1], output[2], output[3]);
    #endif
#endif
        for(int i=0;i<MOTOR_COUNT;i++){
            motor[i]->control(output[i]);
        }

        if (Board::get()->get_tick_count()-last_velocity_command_time>Data_holder::get()->parameter.cmd_last_time){
            for(int i=0;i<MOTOR_COUNT;i++){
                input[i] = 0;
            }
        }
    }
}

运动控制是在Robot类中的do_kinmatics函数实现的,大概流程

  • 根据解算到各个轮子的速度转换到对应编码器的值和编码器的输出计算PID
  • 根据PID结果控制电机
  • 超时判断

上面说到的解算就是在Robot类中的update_velocity函数实现的

void Robot::update_velocity(){
    short vx = min(max(Data_holder::get()->velocity.v_liner_x, -(short(Data_holder::get()->parameter.max_v_liner_x))), short(Data_holder::get()->parameter.max_v_liner_x));
    short vy = min(max(Data_holder::get()->velocity.v_liner_y, -(short(Data_holder::get()->parameter.max_v_liner_y))), short(Data_holder::get()->parameter.max_v_liner_y));
    short vz = min(max(Data_holder::get()->velocity.v_angular_z, -(short(Data_holder::get()->parameter.max_v_angular_z))), short(Data_holder::get()->parameter.max_v_angular_z));

    float vel[3]={vx/100.0, vy/100.0, vz/100.0};
    float motor_speed[MOTOR_COUNT]={0};
    model->motion_solver(vel, motor_speed);


    for(int i=0;i<MOTOR_COUNT;i++){
        input[i] = motor_speed[i]*short(Data_holder::get()->parameter.encoder_resolution)/(2*__PI)*short(Data_holder::get()->parameter.do_pid_interval)*0.001;
    }


#if DEBUG_ENABLE
    printf("vx=%d %d motor_speed=%ld %ld\r\n", vx, vz, long(motor_speed[0]*1000), long(motor_speed[1]*1000));
#endif

    last_velocity_command_time = Board::get()->get_tick_count();
    do_kinmatics_flag = true;
}

通过调用运动模型接口调用运动解算(model->motion_solver(vel, motor_speed)),完成从控制的全局速度(下发的角速度和线速度)到各个轮子速度的转换,最终转换为在do_pid_interval时间内编码器的变化值

3.PIBOT电机方向和顺序

3.1电机方向与pwm_value值关系

PIBOT定义所有电机控制给正值时(motor[i]->control(pwm_value)),从电机输出轴方向看电机顺时针转。
对应到差分小车apollo,

motor[0]->control(2000);//控制左电机向后
motor[1]->control(2000);//控制右电机向前

对与小车就是在逆时针运动

再如

motor[0]->control(-2000);//控制左电机向前
motor[1]->control(2000);//控制右电机向前

对与小车就是在向前运动

总结就是control给定正值,输出轴看电机顺时针转动,反之给定负值逆时针转动

3.2电机顺序

上面可以看出来motor[i]中的索引i

apollo中左电机为0,右电机为1
zeus中前电机为0,左电机为1,右电机为2
hadeshera中右后电机为0,右前电机为1,左前电机为2,左后电机为3
具体编号源码kinematic_models文件夹中相应文件前右说明

3.3 编码器

这里编码器值是一个程序计数值,一个方向转动编码器值会累加,反方向会减少
PIBOT定义上面2.1中给定正值时,编码器值累加,给定负是编码器值减少

4.验证测试

如果上面有点晦涩,那直接按照如下方式测试即可
apollo为例

4.1 测试方向

do_kinmatics直接motor[0]->control(2000); return;

** 这里2000相对于PWM最大值来的Arduino最大1024 STM32最大设置为5000**

观察左电机电机是否向后

分别查看下面各个控制与实际电机转动情况
motor[0]->control(2000) 左电机向后
motor[0]->control(-2000) 左电机向前
motor[1]->control(2000) 右电机向前
motor[1]->control(-2000) 右电机向后

如果得不到相应的结果,根据情况调整:
a. motor[0]->control时右电机转动,可能考虑左右电机接线反了
b. motor[1]->control(xx) ,哪个电机是对的,但发现不对。可以调整电机接线,也可以调整程序的方向控制

PIBOT提供相关的宏MOTORx_REVERSE可以不需要对换电机接线(PIN1和PIN6),达到调整方向的目的

4.2测试编码器

恢复会正常程序并新增调试的输出,连接调试串口,打开串口调试工具
对应到程序的输出


观察串口调试助手中的total_count=xx yy输出,xx随着左轮的向前转动越来越小,反之越来越大;yy随着右轮的向前越来越大,反之越来越小

如果得不到相应的结果,根据情况调整:
a. 左电机转动yy变化或者右电机转动xx变化,那应该是2个电机编码器反了,需要调换下
b. 左电机转动xx变化,但相反。应该是编码器AB相接线反了;右电机同理

PIBOT提供相关的宏ENCODERx_REVERSE可以不需要对换电机编码器接线(PIN3和PIN4),达到调整方向的目的

5.程序修改

最新程序已经支持直接修改配置方式, 具体请参考6.配置修改

固件程序提供了直接的宏,针对电机反向或者编码器反向的问题
例如STM32F1电机方向反转宏,修改宏定义



STM32F4编码器方向反转宏(在param.mk文件)


6. 配置修改

目前最新程序已经支持动态修改无需修改代码重新编译,

# 分别开启2个终端执行下面命令
pibot_bringup
pibot_configure
exchange.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容