第四天:基于遥控器,利用CAN通信和PID控制原理控制电机

目录

一:遥控器移植

1.配置USART3

2.移植遥控器

 二:启用CAN通信

1.Cube中配置can

2.keil关于can的代码移植例程can的.c.h即可

三:根据不同电调的ID控制不同电机

1.can接收中断回调函数的重编写

2.主循环重编写

四:PID计算函数(位置式)


一:遥控器移植

1.配置USART3

(1)设置USART3为异步模式

(2)打开USART3的中断

(3)约定波特率为100000

(4)开启DMA,设为RX接收模式,优先级为非常高

(5)在DMAsetting下设置为循环模式circular

2.移植遥控器

移植大疆官方例程中远程遥控的代码,以实现遥控器发送数据让C板接收:

1.将官方例程中的application和bsp文件夹复制到自己的工程下,然后在keil新建对应的文件夹并将对应的.c.h文件添加进去,并在魔术棒中添加好对应路径(注意bsp文件夹要添加board的路径)。

2.打开官方例程的keil,对照并将#include "remote_control.h"添加到main.c对应位置。

3,复制初始化函数 remote_control_init();到对应位置。

4,编译,报错,找到Cube生成对应代码时生成的和例程中重复定义的中断控制函数:

void USART3_IRQHandler(void),其在application文件夹下的remote_control.c文件夹中。

然后选中此函数,ctrl+F,选择全工程搜索,不断的find next,将会找到重复的函数,删除即可。

5.硬件上将接发器连到C板的DBUS接口上,并给C板连上电池,将遥控器和接发器对好频,无误后再编译,烧录,

6,在USART3_IRQHandler函数处设置断点(灰色区域),使用keil调试模式,进行debug,运行,如果此时推动摇杆能进入到此中断接收函数里说明可以接收遥控器数据了。

7,找到rc_ctrl变量并右键add处设置watch1,在右下角展开其数据,推动摇杆即可实时观察其数据变化了。至此遥控器移植就完成了。

 二:启用CAN通信

1.Cube中配置can

(1)Cube中启用CAN1,CAN2,

(2)配置通信波特率:先将Time Quanta in Bit Segment1和2分别设为10TImes和3Times,再将Prescaler设置为3,然后回车——注意这里要按照此顺序设置,不然报错——这样波特率就设置为100000了。

(3)在NVIC Setting中开启Can1和2的RX0 interrupt,开启接收中断

(4)并且根据开发板原理图可知can1的引脚是不对的,要将PD0引脚设置为CAN1_RX,将PD1设置为CAN1_TX。

2.keil关于can的代码移植例程can的.c.h即可

三:根据不同电调的ID控制不同电机

1.can接收中断回调函数的重编写

(1)在CAN的中断接收回调函数所在文件重新定义存储电机数据的结构数组

motor_data_t      motor_data[4];    //存储电机数据的结构数组

motor_data_t 是一个结构类型重定义:

typedef struct
{
    uint16_t                   ecd;                //电机位置
    int16_t                    speed_rpm;      //电机转速        
    int16_t                    given_current;        //电机收到的电流
    uint8_t                    temperate;            //电机温度
}motor_data_t;

我们定义一个motor_data[4]数组,用来存储最多4个电机的电机数据

(2)在中断回调函数中重新编写判断ID的部分

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
	uint8_t rx_data[8];
   CAN_RxHeaderTypeDef rx_header;	
	HAL_CAN_GetRxMessage(hcan,CAN_RX_FIFO0,&rx_header,rx_data);//´Ó½ÓÊÕ»º³åÇø»ñÈ¡Êý¾Ý	
	if(hcan==&hcan1)//´Ë´¦¸ù¾ÝÄãÓÃcan2»¹ÊÇcan1½ÓÊոıä
	{
	 int i=rx_header.StdId-0x200;     
	 switch(i)
	 {
		 case 1:
			 get_motor_measure(&motor_data[i-1],rx_data);//µç»úÊý¾Ý½âÂë
			 break;
		 case 2:
			 get_motor_measure(&motor_data[i-1],rx_data);//µç»úÊý¾Ý½âÂë
			 break;
		 case 3:
			 get_motor_measure(&motor_data[i-1],rx_data);//µç»úÊý¾Ý½âÂë
			 break;
		 case 4:
			 get_motor_measure(&motor_data[i-1],rx_data);//µç»úÊý¾Ý½âÂë
			 break;		 
    } 
	}
	
}

其中:

HAL_CAN_GetRxMessage(hcan,CAN_RX_FIFO0,&rx_header,rx_data);//从接收缓存区获取数据

get_motor_measure(&motor_data[i-1],rx_data);//电机数据解码

这里switch-case进行ID地址的判断,注意这里学习使用的是C610无刷电机调速器,到大疆官网产品——配件——通用产品——输出系统里面的C610无刷电机调速器处查看网页最下方的使用说明书,可以查看电调ID设置方式,指示灯描述等,我们找到CAN通信协议部分,可以看到接收ID的标识符:0x200和0x1FF,0x200对应电调ID1~4,0x1FF对应电调5~8——而电调闪烁灯闪烁几次对应是ID几,所以电调1的ID是0x201,4的ID是0x204,

而中断中的rx_header.StdId变量即为对应的ID,所以减去0x200即可得到1~4的ID。

于是用判断既可以调用    get_motor_measure  函数对对应ID的电机数据进行解码。

别忘了switch-case要break!

2.主循环重编写

(1)在mian的USER CODE BEGIN PV和 USER CODE END PV注释之间——

pid_t Pid[4];//pid结构数组        
extern RC_ctrl_t rc_ctrl;   //ctrl1是控制摇杆的参数
extern motor_data_t  motor_data[4];//这是刚才CAN中断回调函数里面的电机接收数据数组 

float set_rpm[4];            //电机转速数组
float give_current[4];     //发送给电机的电流数组

其中:

pid_t是有关PID计算函数的结构类型重定义:

typedef struct
{
    float kp;//比例系数
    float ki;//积分系数
    float kd;//微分系数
    
    float error_before;//当前误差
    
    float pout,iout,dout,out;//比例输出,积分输出,微分输出,输出        
    
    float max_iout;//最大积分输出 
    float max_out;//最大输出
    

}pid_t;

 可以看到其中不仅包含p,i,d,还包含每次PID计算需要用到的当前误差,比例输出,积分输出,微分输出,输出,最大积分输出,最大输出等值,那如果要控制多电机,就可以定义一个结构数组,每个结构元素包含对应电机的pid的计算所需的参数。

切忌一个PID结构变量被多个电机调用!!!因为每个电机每次计算的PID各参数值不同。

(2)在begin2和end2注释之间对PID进行初始化

can_filter_init();                   //can过滤器初始化
for(int i=0;i<4;i++)
{
pid_init(&Pid[i],2,0,0,1200,200);        //pid初始化
}
  remote_control_init();            //遥控器初始化

 其中:pid_ind[i],2,0,0,1200,200);右键跳转到定义里面可以知道对应参数:

//pid初始化
void pid_init(pid_t*pid,float kp,float ki,float kd,float max_out,float max_iout)
{
    pid->kp=kp;
    pid->ki=ki;
    pid->kd=kd;
    pid->max_out=max_out;
    pid->max_iout=max_iout;
    pid->error_before=0;
    pid->iout=0;
}

(3)主循环中接收电机反馈数据


     for(int i=0;i<4;i++)
      {
            set_rpm[i]=rc_ctrl.rc.ch[i+1]*10;    
            give_current[i]=PID_calc(&Pid[i],motor_data[i].speed_rpm,set_rpm[i]);   
      }    
      can_cmd_motor(&hcan1,0x200,give_current[0],give_current[1],give_current[2],give_current[3]);            
      HAL_Delay(5); //一定要delay!!!
  }         

主循环中调用上面定义的set_rpm和give_current数组,循环发送数据计算电流,

而PID_calc 即为PID计算函数。

四:PID计算函数(位置式)

float PID_calc(pid_t *pid,float get,float set)
{
    float error=set-get;
    //计算比例输出
    pid->pout=pid->kp*error;
    //计算积分输出
    pid->iout+=pid->ki*error;
    //计算微分输出
    pid->dout=pid->kd*(error-pid->error_before);
    //计算输出
    pid->out=pid->pout+pid->iout+pid->dout;
    //计算当前误差
    pid->error_before=error;
    LimitMax(pid->iout,pid->max_iout);//积分限幅
    LimitMax(pid->out,pid->max_out);//输出限幅
    return pid->out;
}

此函数的三个参数:pid结构,get当前位置,set目标位置。

PID计算公式:

输出=比例输出+积分输出+微分输出

       =比例系数*(目标位置-当前位置)的值+积分系数*(目标位置-当前位置)的积分(累加)

         +微分系数*(当前误差-上次误差)

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值