Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:add motor and encoder based on can-bus and add a kinematics #39

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions encoder/can_encoder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (c) 2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-11-14 Soil_L The first version
*/
#include "can_encoder.h"

#define DBG_SECTION_NAME "can_encoder"
#define DBG_LEVEL DBG_LOG
#include <rtdbg.h>

rt_sem_t rx_sem_g;

rt_err_t encoder_isr(rt_device_t dev, rt_size_t size)
{
rt_sem_release(rx_sem_g);
return RT_EOK;
}

static rt_err_t can_encoder_enable(void *enc)
{
can_encoder_t enc_sub = (can_encoder_t)enc;

volatile rt_err_t res;

//Avoid repeated opening
if(!(enc_sub->can_dev->flag & RT_DEVICE_FLAG_ACTIVATED))
{
res = rt_device_open(enc_sub->can_dev,RT_DEVICE_FLAG_INT_RX|RT_DEVICE_FLAG_INT_TX);
res = rt_device_control(enc_sub->can_dev,RT_CAN_CMD_SET_BAUD,(void *)enc_sub->Baud);
res = rt_device_control(enc_sub->can_dev,RT_CAN_CMD_SET_FILTER,&enc_sub->cfg);
}
res = rt_device_set_rx_indicate(enc_sub->can_dev,encoder_isr);

return RT_EOK;
}

static rt_err_t can_encoder_disable(void *enc)
{
return RT_EOK;
}

static rt_err_t can_encoder_destroy(void *enc)
{
can_encoder_t enc_sub = (can_encoder_t)enc;

volatile rt_err_t res;
res = rt_device_close(enc_sub->can_dev);
rt_sem_delete(*(enc_sub->rx_sem));
rt_free(enc_sub);

return RT_EOK;
}

can_encoder_t can_encoder_create(char *can,struct rt_can_filter_config cfg,rt_uint32_t Baud,rt_sem_t* rx_sem)
{
can_encoder_t new_encoder = (can_encoder_t)encoder_create(sizeof(struct can_encoder),0);
if(new_encoder == RT_NULL)
{
return RT_NULL;
}

new_encoder->can_dev = (rt_device_t)rt_device_find(can);
if (new_encoder->can_dev == RT_NULL)
{
rt_free(new_encoder);
LOG_E("Falied to find device on %s", can);
return RT_NULL;
}

new_encoder->cfg = cfg;
new_encoder->enc.enable = can_encoder_enable;
new_encoder->enc.disable = can_encoder_disable;
new_encoder->enc.destroy = can_encoder_destroy;
new_encoder->Baud = Baud;
new_encoder->rx_sem = rx_sem;
rx_sem_g = *rx_sem;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对于多实例的 encoder, 全局 sem 是个bug。完全可以不用这样做

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本身信号量是一个指针,多个编码器又要用到同一个信号量,所以我传入的是信号量指针,所以这里是指针的指针

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不管是结构体或结构体指针,它是全局的。 如果确定所有情况下用到的都是同一个信号量,为什么还需要用户创建

return new_encoder;
}
28 changes: 28 additions & 0 deletions encoder/can_encoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-11-14 Soil_L The first version
*/
#ifndef _CAN_ENCODER_
#define _CAN_ENCODER_

#include "encoder.h"

struct can_encoder
{
struct encoder enc;
rt_device_t can_dev;
struct rt_can_filter_config cfg;
rt_uint32_t Baud;
rt_sem_t *rx_sem;
rt_uint32_t rpm;
};
typedef struct can_encoder *can_encoder_t;

can_encoder_t can_encoder_create(char *can,struct rt_can_filter_config cfg,rt_uint32_t Baud,rt_sem_t* rx_sem);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xxx_encoder_create 是面向用户的 rx_sem 是否真的必须? 合理?
另外有点疑惑,这个测试过吗,可以正常工作?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 这份提交希望的是对基于can通信的电机设备和编码器设备都能适配,但是无法统一每一种can设备的通信协议,所以每次接收后解包必须由用户完成,所以需要用信号量通知解包开始。
  • 对于测试,其实issue提交之时我的项目就有急迫的需求,所以很快就按照rt-robot的框架写好了,至少正常运行了1个月,但是,没有没有整合进rt-robot中运行,相当于我自己的用户代码,和我其他代码有一些耦合。提交的时候做了一下修改,其中主要是变量名,还有运动学模型的选择那一块。我可以把建立底盘模型的那一块代码给你看。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议这样, 如果通用的不能完整(或很好地)整出来, 那就先直接上专用的,比如直接就针对 C620 电调, 给用户的接口也可以更易用


#endif //CAN_ENCODER_
15 changes: 11 additions & 4 deletions encoder/encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Change Logs:
* Date Author Notes
* 2019-08-26 sogwms The first version
* 2019-11-14 Soil_L motified for the need of can encoder
*/

#include <rtdevice.h>
Expand Down Expand Up @@ -96,10 +97,16 @@ rt_int16_t encoder_measure_cps(encoder_t enc)
rt_int16_t encoder_measure_rpm(encoder_t enc)
{
RT_ASSERT(enc != RT_NULL);

// return resolution per minute
rt_int16_t res_rpm = encoder_measure_cps(enc) * 60 / enc->pulse_revol;

rt_int16_t res_rpm;
if(enc->sample_time == 0)
{
res_rpm = enc->pulse_count;
}
else
{
// return resolution per minute
res_rpm = encoder_measure_cps(enc) * 60 / enc->pulse_revol;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么要做这个修改? 对can电机获取编码器信息有帮助吗? 对于像大疆can电机 encoder_measure_cps 才是障碍吧, 抽象程度不够

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为对于利用计数脉冲的编码器来说有采样周期,而对于利用通信协议传输数据的编码器来说没有采样周期。所以在这里有一个判断,如果sample_time>0则有采样周期,计算res_rpm的方法不变,如果sample_time<=0则表示没有采样周期,res_rpm由pulse_count直接得到。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可行

return res_rpm;
}

Expand Down
62 changes: 46 additions & 16 deletions kinematics/kinematics.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Change Logs:
* Date Author Notes
* 2019-07-17 Wu Han The first version
* 2019-11-14 Soil_L add four wheel omnidirectional chassis
*/

#include "kinematics.h"
Expand Down Expand Up @@ -46,7 +47,11 @@ kinematics_t kinematics_create(enum base k_base, float length_x, float length_y,
{
new_kinematics->total_wheels = 4;
}

if(k_base == FOUR_WD_ALLDIR)
{
new_kinematics->total_wheels = 4;
}

return new_kinematics;
}

Expand Down Expand Up @@ -136,6 +141,13 @@ void kinematics_get_rpm(struct kinematics kin, struct velocity target_vel, rt_in
res_rpm[2] = cal_rpm.motor3;
res_rpm[3] = cal_rpm.motor4;
}
else if(kin.k_base == FOUR_WD_ALLDIR) //FRONT:0 BACK:1 LEFT:2 RIGHT:3
{
res_rpm[0] = x_rpm + tan_rpm;
res_rpm[1] = -x_rpm + tan_rpm;
res_rpm[2] = y_rpm + tan_rpm;
res_rpm[3] = -y_rpm + tan_rpm;
}
else
{
return;
Expand All @@ -158,25 +170,43 @@ void kinematics_get_velocity(struct kinematics kin, struct rpm current_rpm, stru
if(kin.k_base == FOUR_WD) total_wheels = 4;
if(kin.k_base == ACKERMANN) total_wheels = 2;
if(kin.k_base == MECANUM) total_wheels = 4;
if(kin.k_base == FOUR_WD_ALLDIR) total_wheels = 4;

float average_rps_x;
float average_rps_y;
float average_rps_a;

//convert average revolutions per minute to revolutions per second
average_rps_x = ((float)(current_rpm.motor1 + current_rpm.motor2 + current_rpm.motor3 + current_rpm.motor4) / total_wheels) / 60; // RPM
res_vel.linear_x = average_rps_x * kin.wheel_cir; // m/s

//convert average revolutions per minute in y axis to revolutions per second
average_rps_y = ((float)(-current_rpm.motor1 + current_rpm.motor2 + current_rpm.motor3 - current_rpm.motor4) / total_wheels) / 60; // RPM
if(kin.k_base == MECANUM)
res_vel.linear_y = average_rps_y * kin.wheel_cir; // m/s
else
res_vel.linear_y = 0;

//convert average revolutions per minute to revolutions per second
average_rps_a = ((float)(-current_rpm.motor1 + current_rpm.motor2 - current_rpm.motor3 + current_rpm.motor4) / total_wheels) / 60;
res_vel.angular_z = (average_rps_a * kin.wheel_cir) / ((kin.length_x / 2) + (kin.length_y / 2)); // rad/s

if(kin.k_base == TWO_WD || kin.k_base == FOUR_WD || kin.k_base == ACKERMANN)
{
//convert average revolutions per minute to revolutions per second
average_rps_x = ((float)(current_rpm.motor1 + current_rpm.motor2 + current_rpm.motor3 + current_rpm.motor4) / total_wheels) / 60; // RPM
res_vel.linear_x = average_rps_x * kin.wheel_cir; // m/s

//convert average revolutions per minute in y axis to revolutions per second
average_rps_y = ((float)(-current_rpm.motor1 + current_rpm.motor2 + current_rpm.motor3 - current_rpm.motor4) / total_wheels) / 60; // RPM
if(kin.k_base == MECANUM)
res_vel.linear_y = average_rps_y * kin.wheel_cir; // m/s
else
res_vel.linear_y = 0;

//convert average revolutions per minute to revolutions per second
average_rps_a = ((float)(-current_rpm.motor1 + current_rpm.motor2 - current_rpm.motor3 + current_rpm.motor4) / total_wheels) / 60;
res_vel.angular_z = (average_rps_a * kin.wheel_cir) / ((kin.length_x / 2) + (kin.length_y / 2)); // rad/s
}
if(kin.k_base == FOUR_WD_ALLDIR)//FRONT:motor1 BACK:motor2 LEFT:motor3 RIGHT:motor4
{
//convert average revolutions per minute to revolutions per second
average_rps_x = ((float)(current_rpm.motor1 - current_rpm.motor2) / 2) / 60; // RPM
res_vel.linear_x = average_rps_x * kin.total_wheels; // m/s

//convert average revolutions per minute in y axis to revolutions per second
average_rps_y = ((float)(current_rpm.motor3 - current_rpm.motor4) / 2) / 60; // RPM
res_vel.linear_y = average_rps_y * kin.wheel_cir; // m/s

//convert average revolutions per minute to revolutions per second
average_rps_a = ((float)(current_rpm.motor1 + current_rpm.motor2 + current_rpm.motor3 + current_rpm.motor4
- 2*60*(average_rps_x+average_rps_y)) / total_wheels) / 60;
res_vel.angular_z = (average_rps_a * kin.wheel_cir) / ((kin.length_x / 2) + (kin.length_y / 2)); // rad/s
}
rt_memcpy(velocity, &res_vel, sizeof(struct velocity));
}
4 changes: 3 additions & 1 deletion kinematics/kinematics.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Change Logs:
* Date Author Notes
* 2019-07-17 Wu Han The first version
* 2019-11-14 Soil_L add four wheel omnidirectional
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议分开提交 motor 和 kinematic

*/

#ifndef __KINEMATICS_H__
Expand All @@ -17,7 +18,8 @@ enum base {
TWO_WD = 0,
FOUR_WD,
ACKERMANN,
MECANUM
MECANUM,
FOUR_WD_ALLDIR
};

// rad/min
Expand Down
96 changes: 96 additions & 0 deletions motor/can_motor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-11-14 Soil_L The first version
*/
#include "can_motor.h"

#define DBG_SECTION_NAME "can_motor"
#define DBG_LEVEL DBG_LOG
#include <rtdbg.h>


static rt_err_t can_motor_enable(void *mot)
{
can_motor_t mot_sub = (can_motor_t)mot;

//Avoid repeated opening
if(!(mot_sub->can_dev->flag & RT_DEVICE_FLAG_ACTIVATED))
{
rt_err_t res = rt_device_open(mot_sub->can_dev,RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX );
res = rt_device_control(mot_sub->can_dev,RT_CAN_CMD_SET_BAUD,(void *)mot_sub->Baud);
res = rt_device_control(mot_sub->can_dev,RT_CAN_CMD_SET_FILTER,&mot_sub->cfg);
}
return RT_EOK;
}

static rt_err_t can_motor_disable(void *mot)
{
can_motor_t mot_sub = (can_motor_t)mot;

rt_err_t res = rt_device_close(mot_sub->can_dev);

return RT_EOK;
}

static rt_err_t can_motor_reset(void *mot)
{
return RT_EOK;
}

static rt_err_t can_motor_set_speed(void *mot, rt_int16_t thousands)
{
can_motor_t mot_sub = (can_motor_t)mot;

if(mot_sub->min_num > thousands)
{
thousands = mot_sub->min_num;
}
else if(mot_sub->max_num < thousands)
{
thousands = mot_sub->max_num;
}
Comment on lines +45 to +56
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

额, 此处的 thousands 默认即暗示设定范围为 -1000 ~ 1000, 将此映射到实际可设置范围即可。 可参考其他已有 motor

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的thousands是什么含义?我的理解是控制器计算完之后给电机执行的输出

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是的,但值的范围是确定的。 打个比方, 电机是小弟,上面的大哥给它设定值让它运转。 小弟和大哥都公认 设定值的范围是 -1000~+1000. 小弟(电机)多种多样(最大转速等)。 但都知道 +-1000 对应自己的最大转速, 0是停止


mot_sub->msg->data[(mot_sub->mot_id -1)*2] = thousands>>8;
mot_sub->msg->data[(mot_sub->mot_id -1)*2+1] = thousands;
rt_size_t size = rt_device_write(mot_sub->can_dev, 0, mot_sub->msg, sizeof(*(mot_sub->msg)) );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这是只针对大疆can电机的协议吗,是的话文件名等应该更具体一点 比如 dji_can_motor

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

想的是对所有用can通信的电机和编码器。测试的时候用的是大疆M3508电机(C620电调)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不是很了解 can 电机 不知 大疆系的can电机协议是否都相同(似)可统一,非大疆的can电机有没有,协议怎样。 可以的话还是建议在能保证完整性的前提下提高适用层次。 建议针 c602 或 大疆系?


return RT_EOK;
}


can_motor_t can_motor_create(char *can,struct rt_can_filter_config cfg,rt_uint32_t Baud,rt_can_msg_t msg,rt_int16_t max_num,rt_int16_t min_num,rt_uint8_t mot_id)
{
RT_ASSERT(max_num>min_num)

can_motor_t new_motor = (can_motor_t)motor_create(sizeof(struct can_motor));
if (new_motor == RT_NULL)
{
return RT_NULL;
}

new_motor->can_dev = (rt_device_t)rt_device_find(can);
if (new_motor->can_dev == RT_NULL)
{
rt_free(new_motor);
LOG_E("Falied to find device on %s", can);
return RT_NULL;
}

new_motor->cfg = cfg;
new_motor->Baud = Baud;
new_motor->msg = msg;
new_motor->max_num = max_num;
new_motor->min_num = min_num;
new_motor->mot_id = mot_id;
new_motor->mot.enable = can_motor_enable;
new_motor->mot.set_speed = can_motor_set_speed;
new_motor->mot.reset = can_motor_reset;
new_motor->mot.disable = can_motor_disable;

return new_motor;
}
30 changes: 30 additions & 0 deletions motor/can_motor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-11-14 Soil_L The first version
*/
#ifndef _CAN_MOTOR_H_
#define _CAN_MOTOR_H_

#include "motor.h"

struct can_motor
{
struct motor mot;
rt_device_t can_dev;
struct rt_can_filter_config cfg;
rt_uint32_t Baud;
rt_can_msg_t msg; //the pointer of message defined by the users,it decide the number of the can motor in the can net
rt_int16_t max_num;
rt_int16_t min_num;
rt_uint8_t mot_id; //the id of the can motor in the can net
};
typedef struct can_motor *can_motor_t;

can_motor_t can_motor_create(char *can,struct rt_can_filter_config cfg,rt_uint32_t Baud,rt_can_msg_t msg,rt_int16_t max_num,rt_int16_t min_num,rt_uint8_t mot_id);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同样的问题 api是否合适? 需要msg?需要 maximum?

还有想问下这是针对哪种can电机的? 还是通用的?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 因为常常是多个电机挂在同一个can网络上,用ID来区分不同的电机数据,所以希望用户先把每一个电机的msg里的ID设置好
  • 不同的电机有不同的最大设置值,max_num是为了适配不同的电机的最大设置值


#endif // __CAN_MOTOR_H__