CAN总线控制伺服电机详解

硬件

用的是C12B驱动器,支持485/CAN,PWM,232通信。

image-20230407212824221

可以CAN通信的伺服电机

image-20230407212923796

image-20230405154636987

CAN发送格式,image-20230407204840243

上面是在说明书上截取的部分图片。

用的STM32F103系列芯片,参考杨桃电子的CAN通信驱动,

双模式

`	//位置模式
		buff[0]=0x00;
		buff[1]=0x1A;//写数据不保存
		buff[2]=0x50;//位置模式选择/0x50
		buff[3]=0x00;
		buff[4]=0x00;//0xd0PC数字输入/0xc0外部脉冲
		buff[5]=0x05;//位置调试模式低16位数据输入
		buff[6]=0x27;
		buff[7]=0x10;
		CAN_Send_Msg(buff,8);
	//力矩模式
	  buff[0]=0x00;
		buff[1]=0x1A;//写数据不保存
		buff[2]=0x06;
		buff[3]=speed>>8;
		buff[4]=speed;
		buff[5]=0x00;
		buff[6]=0x00;
		buff[7]=0x01;
		`

期间我还为CAN通信加了一个PID,但是伺服电机可以不加软件PID,伺服电机与驱动器会形成闭环,我们只需要接收驱动器发回来的数据就可以了。

PID

``

/
  `****************************(C) COPYRIGHT 2019 DJI****************************`
  * `@file       pid.c/h`
  * `@brief      pid实现函数,包括初始化,PID计算函数,`
    `*/`

`#include "pid.h"`
`#include "main.h"`

`/**`
  * `@brief          pid struct data init`
  * `@param[out]     pid: PID struct data point`
  * `@param[in]      mode: PID_POSITION: normal pid`
  *                 `PID_DELTA: delta pid`
  * `@param[in]      PID: 0: kp, 1: ki, 2:kd`
  * `@param[in]      max_out: pid max out`
  * `@param[in]      max_iout: pid max iout`
  * `@retval         none`
    `*/`
    `/**`
  * `@brief          pid struct data init`
  * `@param[out]     pid: PID结构数据指针`
  * `@param[in]      mode: PID_POSITION:普通PID`
  *                 `PID_DELTA: 差分PID`
  * `@param[in]      PID: 0: kp, 1: ki, 2:kd`
  * `@param[in]      max_out: pid最大输出`
  * `@param[in]      max_iout: pid最大积分输出`
  * `@retval         none`
    `*/`
    `void PID_init(pid_type_def *pid, uint8_t mode, const fp32 PID[3], fp32 max_out, fp32 max_iout)`
    `{`
    `if (pid == NULL || PID == NULL)`
    `{`
        `return;`
    `}`
    `pid->mode = mode;`
    `pid->Kp = PID[0];`
    `pid->Ki = PID[1];`
    `pid->Kd = PID[2];`
    `pid->max_out = max_out;`
    `pid->max_iout = max_iout;`
    `pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;`
    `pid->error[0] = pid->error[1] = pid->error[2] = pid->Pout = pid->Iout = pid->Dout = pid->out = 0.0f;`
    `}`

`/**`
  * `@brief          pid calculate` 
  * `@param[out]     pid: PID struct data point`
  * `@param[in]      ref: feedback data` 
  * `@param[in]      set: set point`
  * `@retval         pid out`
    `*/`
    `/**`
  * `@brief          pid计算`
  * `@param[out]     pid: PID结构数据指针`
  * `@param[in]      ref: 反馈数据`
  * `@param[in]      set: 设定值`
  * `@retval         pid输出`
    `*/`
	`fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set)`
	`{`
    `if (pid == NULL)`
    `{`
        `return 0.0f;`
    `}`

    `pid->error[2] = pid->error[1];`
    `pid->error[1] = pid->error[0];`
    `pid->set = set;`
    `pid->fdb = ref;`
    `pid->error[0] = set - ref;`
    `if (pid->mode == PID_POSITION)`
    `{`
        `pid->Pout = pid->Kp * pid->error[0];`
        `pid->Iout += pid->Ki * pid->error[0];`
        `pid->Dbuf[2] = pid->Dbuf[1];`
        `pid->Dbuf[1] = pid->Dbuf[0];`
        `pid->Dbuf[0] = (pid->error[0] - pid->error[1]);`
        `pid->Dout = pid->Kd * pid->Dbuf[0];`
        `LimitMax(pid->Iout, pid->max_iout);`
        `pid->out = pid->Pout + pid->Iout + pid->Dout;`
        `LimitMax(pid->out, pid->max_out);`
    `}`
    `else if (pid->mode == PID_DELTA)`
    `{`
        `pid->Pout = pid->Kp * (pid->error[0] - pid->error[1]);`
        `pid->Iout = pid->Ki * pid->error[0];`
        `pid->Dbuf[2] = pid->Dbuf[1];`
        `pid->Dbuf[1] = pid->Dbuf[0];`
        `pid->Dbuf[0] = (pid->error[0] - 2.0f * pid->error[1] + pid->error[2]);`
        `pid->Dout = pid->Kd * pid->Dbuf[0];`
        `pid->out += pid->Pout + pid->Iout + pid->Dout;`
        `LimitMax(pid->out, pid->max_out);`
    `}`
    `return pid->out;`
	````	

`}`

`/**`
  * `@brief          pid out clear`
  * `@param[out]     pid: PID struct data point`
  * `@retval         none`
    `*/`
    `/**`
  * `@brief          pid 输出清除`
  * `@param[out]     pid: PID结构数据指针`
  * `@retval         none`
    `*/`
    `void PID_clear(pid_type_def *pid)`
    `{`
    `if (pid == NULL)`
    `{`
        `return;`
    `}`

    `pid->error[0] = pid->error[1] = pid->error[2] = 0.0f;`
    `pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;`
    `pid->out = pid->Pout = pid->Iout = pid->Dout = 0.0f;`
    `pid->fdb = pid->set = 0.0f;`
    ``}``

CAN的发送和接收

`#include "can.h"
#include "delay.h"
#include "oled0561.h"
#include "stm32f10x_can.h"
#include "pid.h"
//#include "main.h"

	u8 x;
	motor_measure_t motor_chassis[7];
	extern pid_type_def chassis_pid[4];
	 extern int  s;
	extern int16_t speedset;  
//	u8 CAN_ReceiveBuff[8];//有八个数据



u8 CAN1_Configuration(void){ //CAN初始化(返回0表示设置成功,返回其他表示失败)
    GPIO_InitTypeDef        GPIO_InitStructure; 
    CAN_InitTypeDef         CAN_InitStructure;
    CAN_FilterInitTypeDef   CAN_FilterInitStructure;

#if CAN_INT_ENABLE
    NVIC_InitTypeDef        NVIC_InitStructure;	 
#endif
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PORTA时钟                                                                
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);    //使能CAN1时钟  
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化IO
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
	GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化IO
    //CAN单元设置
    CAN_InitStructure.CAN_TTCM=DISABLE;         //非时间触发通信模式  
    CAN_InitStructure.CAN_ABOM=DISABLE;         //软件自动离线管理   
    CAN_InitStructure.CAN_AWUM=DISABLE;         //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
    CAN_InitStructure.CAN_NART=ENABLE;          //禁止报文自动传送 
    CAN_InitStructure.CAN_RFLM=DISABLE;         //报文不锁定,新的覆盖旧的  
    CAN_InitStructure.CAN_TXFP=DISABLE;         //优先级由报文标识符决定 
    CAN_InitStructure.CAN_Mode= CAN_Mode_Normal;  //模式设置:CAN_Mode_Normal 普通模式,CAN_Mode_LoopBack 回环模式; 
    //设置波特率
    CAN_InitStructure.CAN_SJW=tsjw;             //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位  CAN_SJW_1tq    CAN_SJW_2tq CAN_SJW_3tq CAN_SJW_4tq
    CAN_InitStructure.CAN_BS1=tbs1;             //Tbs1=tbs1+1个时间单位CAN_BS1_1tq ~ CAN_BS1_16tq
    CAN_InitStructure.CAN_BS2=tbs2;             //Tbs2=tbs2+1个时间单位CAN_BS2_1tq ~ CAN_BS2_8tq
    CAN_InitStructure.CAN_Prescaler=brp;        //分频系数(Fdiv)为brp+1  
    CAN_Init(CAN1, &CAN_InitStructure);         //初始化CAN1 
	//设置过滤器
    CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器0
    CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;   //屏蔽位模式
    CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;  //32位宽 
    CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;    //32位ID
    CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;		//
    CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32位MASK
    CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
    CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//过滤器0关联到FIFO0
    CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;//激活过滤器0
    CAN_FilterInit(&CAN_FilterInitStructure);           //滤波器初始化

#if CAN_INT_ENABLE 	//以下是用于CAN中断方式接收的设置
    CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);              //FIFO0消息挂号中断允许.            
    NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     // 主优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            // 次优先级为0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
#endif
    return 0;		
}
//电机运行程序
u8 buff[8];//有八个数据
void motor_can(u16 speed){
		buff[0]=0x00;
		buff[1]=0x1A;
		buff[2]=0x06;
		buff[3]=speed>>8;
		buff[4]=speed;
		buff[5]=0x00;
		buff[6]=0x00;
		buff[7]=0x00;
		CAN_Send_Msg(buff,8);
	delay_s(2);
	
		buff[0]=0x00;
		buff[1]=0x1A;
		buff[2]=0x06;
		buff[3]=speed>>8;
		buff[4]=speed;
		buff[5]=0x00;
		buff[6]=0x00;
		buff[7]=0x01;
		CAN_Send_Msg(buff,8);
//	//位置模式
//		buff[0]=0x00;
//		buff[1]=0x1A;//写数据不保存
//		buff[2]=0x50;//位置模式选择/0x50
//		buff[3]=0x00;
//		buff[4]=0x00;//0xd0PC数字输入/0xc0外部脉冲
//		buff[5]=0x05;//位置调试模式低16位数据输入
//		buff[6]=0x27;
//		buff[7]=0x10;
//		CAN_Send_Msg(buff,8);
//	//力矩模式
//	  buff[0]=0x00;
//		buff[1]=0x1A;//写数据不保存
//		buff[2]=0x06;
//		buff[3]=speed>>8;
//		buff[4]=speed;
//		buff[5]=0x00;
//		buff[6]=0x00;
//		buff[7]=0x01;
//		
		delay_s(5);


		} 

//CAN发送一组数据(固定格式:ID为0X12,标准帧,数据帧)   
//msg:数据指针,最大为8个字节,len:数据长度(最大为8) 
//返回值:0,成功; 其他,失败;
u8 CAN_Send_Msg(u8* msg,u8 len){  //u8* msg 指向要发送的数据部分
    u8 mbox;
    u16 i=0;
    CanTxMsg TxMessage;
    TxMessage.StdId=5;           // 标准标识符 
    TxMessage.ExtId=0x000;           // 设置扩展标识符
    TxMessage.IDE=CAN_Id_Standard;  // 标准帧
    TxMessage.RTR=CAN_RTR_Data;     // 数据帧
    TxMessage.DLC=len;              // 要发送的数据长度
    for(i=0;i<len;i++)
    TxMessage.Data[i]=msg[i];       //写入数据              
    mbox= CAN_Transmit(CAN1,&TxMessage);   
    i=0; 
    while((CAN_TransmitStatus(CAN1,mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++; //等待发送结束
    if(i>=0XFFF)return 1;
    return 0;    
}

//can口接收数据查询
//buf:数据缓存区;     
//返回值:0,无数据被收到,其他,接收的数据长度;
u8 CAN_Receive_Msg(u8 *buf){                  
    u32 i;
    CanRxMsg RxMessage;
    if(CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;//没有接收到数据,直接退出 
    CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);//读取数据 
    for(i=0;i<8;i++) //把8个数据放入参数数组
    buf[i]=RxMessage.Data[i];  
    return RxMessage.DLC;  //返回数据数量 
}



#define get_motor_measure(ptr, data)                                    \
    {                                                                   \
        (ptr)->last_ecd = (ptr)->ecd;                                   \
        (ptr)->ecd = (uint16_t)((data)[0] << 8 | (data)[1]);            \
        (ptr)->speed_rpm = (uint16_t)((data)[6] <<8 | (data)[7]);      \
        (ptr)->given_current = (uint16_t)((data)[4] << 8 | (data)[5]);  \
        (ptr)->temperate = (data)[5];                                   \
    }//
		
//CAN的中断接收程序(中断处理程序)	
//必须在can.h文件里CAN_INT_ENABLE为1才能使用中断
//数据处理尽量在中断函数内完成,外部处理要在处理前关CAN中断,防止数据覆盖

void USB_LP_CAN1_RX0_IRQHandler(void){
    CanRxMsg RxMessage;
	vu8 CAN_ReceiveBuff[8]; //CAN总线中断接受的数据寄存器
    vu8 i = 0;
    vu8 u8_RxLen = 0;
  	CAN_ReceiveBuff[0] = 0;	//清空寄存器
    RxMessage.StdId = 5;
    RxMessage.ExtId = 0x00;
    RxMessage.IDE = 0;
    RxMessage.RTR = 0;
    RxMessage.DLC = 0;
    RxMessage.FMI = 0;
    for(i=0;i<8;i++){
        RxMessage.Data[i]=0x00; 
    } 
    CAN_Receive(CAN1,CAN_FIFO0,&RxMessage); //读出FIFO0数据
    u8_RxLen = RxMessage.DLC; //读出数据数量
    if(RxMessage.StdId==5){//判断ID是否一致  
		CAN_ReceiveBuff[i] = RxMessage.DLC; //将收到数据数量放到数组0的位置
        for( i=0;i<u8_RxLen; i++){ //将收到的数据存入CAN寄存器
            CAN_ReceiveBuff[i] = RxMessage.Data[i]; //将8位数据存入CAN接收寄存器

//		//CAN查寻方式的接收处理

  get_motor_measure(&motor_chassis[i], CAN_ReceiveBuff);
//if(motor_chassis[0].temperate==0xe4){
//		motor_chassis[0].speed_rpm=200;			
		OLED_DISPLAY_8x16(6,8*8,motor_chassis[0].speed_rpm/100+0x30);//
		OLED_DISPLAY_8x16(6,9*8,(motor_chassis[0].speed_rpm%100)/10+0x30);//
		OLED_DISPLAY_8x16(6,10*8,motor_chassis[0].speed_rpm%10+0x30);//
//		OLED_DISPLAY_8x16(6,11*8,CAN_ReceiveBuff[7]%10+0x30);//
		OLED_DISPLAY_8x16(6,12*8,'n');//
//}
//					s=115;
					OLED_DISPLAY_8x16(6,13*8,s/100+0x30);//
					OLED_DISPLAY_8x16(6,14*8,(s%100)/10+0x30);//
					OLED_DISPLAY_8x16(6,15*8,s%10+0x30);//
						PID_calc(&chassis_pid[0],motor_chassis[0].speed_rpm, speedset);//----pid计算
	        motor_can(chassis_pid[0].out);
    delay_ms(1000);
		s=266;

    delay_ms(100);
    	NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn); //失能CAN1消息接收中断 USB_LP_CAN1_RX0_IRQn
    CAN_ClearITPendingBit(CAN1,CAN_IT_FMP0);//清除中断标志位
    	CAN_FIFORelease(CAN1,CAN_FIFO0);//释放FIFO0 
    	CAN_ITConfig(CAN1,CAN_IT_FMP0, DISABLE);
//    NVIC_InitTypeDef.NVIC_IRQChannelCmd = ENABLE;
        }
    }
}

`

主函数

`#include "stm32f10x.h" //STM32头文件
#include "sys.h"
#include "delay.h"
#include "touch_key.h"
#include "relay.h"
#include "oled0561.h"
#include "main.h"
#include "can.h"
#include "pid.h"
#include "struct_typedef.h"

 vu8 CAN_ReceiveBuff[8];

pid_type_def chassis_pid[4];
uint8_t i=0;
int  s;
 extern motor_measure_t  motor_chassis[7];
int16_t speed_rpm;
 int16_t speedset=200;   //

 const static fp32 PID[3] = {5.0f, 0.0f, 0.0f};

int main (void){//主程序
extern	u8 buff[8];//有八个数据
//	u8 x;
	delay_ms(100); //上电时等待其他器件就绪
	RCC_Configuration(); //系统时钟初始化 
//	TOUCH_KEY_Init();//触摸按键初始化
	RELAY_Init();//继电器初始化

	CAN1_Configuration(); //CAN总线初始化 返回0表示成功
	
	I2C_Configuration();//I2C初始化
	OLED0561_Init(); //OLED初始化
	OLED_DISPLAY_8x16_BUFFER(0,"   dgut-lyl "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(2,"   CAN TEST  "); //显示字符串
	OLED_DISPLAY_8x16_BUFFER(6,"  speed:"); //显示字符串


	 PID_init(&chassis_pid[i], PID_DELTA, PID, 16000.0f, 2000.0f);
	
	while(1){

//
	PID_calc(&chassis_pid[0],motor_chassis[0].speed_rpm,speedset);//----pid计算
	motor_can(chassis_pid[0].out);
    delay_ms(1000);
		s=266;

}`

最终效果

6868a915181dc10bb4d3410bb7b1eb0
nit(); //OLED初始化
OLED_DISPLAY_8x16_BUFFER(0," dgut-lyl “); //显示字符串
OLED_DISPLAY_8x16_BUFFER(2,” CAN TEST “); //显示字符串
OLED_DISPLAY_8x16_BUFFER(6,” speed:"); //显示字符串

 PID_init(&chassis_pid[i], PID_DELTA, PID, 16000.0f, 2000.0f);

while(1){

//
PID_calc(&chassis_pid[0],motor_chassis[0].speed_rpm,speedset);//—-pid计算
motor_can(chassis_pid[0].out);
delay_ms(1000);
s=266;

}`

最终效果

[外链图片转存中…(img-SQsUhaAr-1680877483076)]

物联沃分享整理
物联沃-IOTWORD物联网 » CAN总线控制伺服电机详解

发表回复