一. 简介
之前写过一篇文章STM32实现四驱小车(五)电机控制任务——电机速度PID控制算法,其中是以大疆的M3508电机为例进行讲解的(没错,就是RoboMaster机器人同款电机,不过Robomaster上的电机好像是小一号的M2006)。不少小伙伴私信问我要代码,我都回复说不是有官方demo么。后来问的人多了我大概明白了,看来官方的Demo还是有点门槛。可能是带FreeRTOS操作系统看不懂,或者对CAN通信一知半解,或者电机PID控制理解不了,或者代码框架不太能跟上。于是决定单独写一篇专门讲解大疆M3508电机使用CAN通信进行速度闭环控制的文章。
本文分别使用CAN查询接收与CAN中断接收两种方式改写了官方Demo,两者都去掉了操作系统,使代码精简易读。
本文环境:
- Keil MDK5.14
- STM32CubeMX6.2.1
- 开发板/芯片:正点原子探索者F407/正点原子阿波罗F767IGT6
实现功能:
- 大疆M3508速度PID控制
- 说明:CAN查询接收与中断接收两种方式,无操作系统
下载链接:
二. 电机通信协议
关于电机的说明请参照电机配套的C620电调说明书,这里简单介绍一下。C620电调可以通过两种方式控制电机,一种是接收PWM信号,这种方式接线与控制都比较简单,直接使用PWM进行速度控制(已经是闭环),另一种方式就是CAN通信,这种方式电调定期上传电机的电流、速度、位置等信息,控制器读取CAN报文之后通过PID计算得到输出量,再通过CAN总线下发报文,从而实现速度PID控制。虽然PWM方式简单易行,但经实际测试效果并不好,速度稳定性不行。
CAN上传报文:
CAN上传报文是电调发给单片机的,上传电机数据。
标识符:0×200+电调ID (如:ID为1,该标识符为0×201) 帧类型:标准帧 帧格式:DATA DLC:8字节 发送频率:1KHz(默认值,可在 RoboMaster Assistant软件中修改发送频率) 转子机械角度值范围:0~ 8191(对应转子机械角度为0~360°) 转子转速值的单位为:RPM 电机温度的单位为:℃
可以看到每个电调以1KHz的频率上报电机数据,每帧数据包含电机的位置、转速、转矩电流、温度四个信息量,我们最关注的就是转速,通过上报的转速与设定的速度比较,进行PID控制。
CAN下发报文:
CAN下发报文是单片机发给电调的,设置电调的电流输出
两个标识符(0200和0x1FF)各自对应控制4个电调。控制电流值范围-16384~ 0~ 16384,对应电调输出的转矩电流范围-20~ 0~ 20A。
标识符: 0x200 帧格式:DATA 帧类型: 标准帧 DLC:8字节 标识符: 0x1FF 帧格式:DATA 帧类型: 标准帧 DLC:8字节 下发的报文只包含电流值信息,这是PID计算的输出结果,至于为什么是电流后面会说,每帧数据控制四个电机,最多通过两组不同ID的报文控制8个电机。
其他的设置电调ID的方法以及校准、指示灯等请阅读说明书。
三. 电机PID控制原理
电机是一种通电就会转的东东,正负极反接就会反转,电压增大转速就会增大,但是我们希望能够精准控制速度,甚至控制它的位置,所以需要有电机驱动器。最低级的电机驱动器只提供功率放大或整流,PID计算需要通过控制器计算;一些高级点的驱动器内部做了PID控制,控制器只需要给速度指令或位置指令就行了,这就是控制的分层。我们使用的M3508电机配套的C620电调,在用PWM控制时它是自带速度PID的,使用CAN总线控制时它就不带PID,需要单片机来计算。
伺服电机有位置伺服、速度伺服、力伺服,大家可能也听过电机的三闭环控制,也就是位置环——速度环——电流环,如图所示,根据控制目标的不同,从外环到内环分别对应电机的位置伺服、速度伺服与力伺服,也可以说三种模式:位置模式、速度模式、转矩模式。
我们的目标是控制速度,上图变成下面这样:
加减速处理的目的是防止偏差值太大,导致速度环PID输出结果太大,超过电机的响应范围,结果可能导致死机、断续旋转。3508就有这个问题的,加入加减速就是让电机速度变化更加平缓。
由于速度换输出的是电流值,所以这也解释了为什么单片机通过CAN报文下发的是转矩电流。至于电流环在哪?那是硬件决定的。
四. 官方代码移植-中断接收
好啦,通信协议和PID原理都搞通了,下面开始写代码。官方提供的例程写的真心很好的,虽然带了个FreeRTOS操作系统,但实际上任务太简单单一,操作系统根本没用上。我们移植它。官方的demo是STM32F4的,这里还是使用STM32F4,实际上我自己用的STM32F7,由于F4和F7都是M4内核,实际上代码是几乎一样的。而使用STM32F1需要注意,F1使用的M3内核,很多寄存器都是不一样的,HAL库就有很大差异,所以驱动部分需要改写。
1. CAN初始化:
创建can.h文件:
#ifndef __CAN_H
#define __CAN_H
#include "sys.h"
#define CAN1_RX0_INT_ENABLE 1
u8 CAN1_Mode_Init(u32 tsjw,u32 tbs2,u32 tbs1,u16 brp,u32 mode);
u8 CAN1_Send_Msg(u8* msg,u8 len);
u8 CAN1_Receive_Msg(u8 *buf);
extern CAN_HandleTypeDef CAN1_Handler;
#endif
这里面有一个宏定义CAN1_RX0_INT_ENABLE ,定义是否使用CAN接收中断,这里我们设为1,使用它。
创建can.c文件:
#include "can.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
CAN_HandleTypeDef CAN1_Handler;
CanTxMsgTypeDef TxMessage;
CanRxMsgTypeDef RxMessage;
u8 CAN1_Mode_Init(u32 tsjw, u32 tbs2, u32 tbs1, u16 brp, u32 mode)
{
CAN_FilterConfTypeDef CAN1_FilerConf;
CAN1_Handler.Instance = CAN1;
CAN1_Handler.pTxMsg = &TxMessage;
CAN1_Handler.pRxMsg = &RxMessage;
CAN1_Handler.Init.Prescaler = brp;
CAN1_Handler.Init.Mode = mode;
CAN1_Handler.Init.SJW = tsjw;
CAN1_Handler.Init.BS1 = tbs1;
CAN1_Handler.Init.BS2 = tbs2;
CAN1_Handler.Init.TTCM = DISABLE;
CAN1_Handler.Init.ABOM = DISABLE;
CAN1_Handler.Init.AWUM = DISABLE;
CAN1_Handler.Init.NART = ENABLE;
CAN1_Handler.Init.RFLM = DISABLE;
CAN1_Handler.Init.TXFP = DISABLE;
if (HAL_CAN_Init(&CAN1_Handler) != HAL_OK)
return 1;
CAN1_FilerConf.FilterIdHigh = 0X0000;
CAN1_FilerConf.FilterIdLow = 0X0000;
CAN1_FilerConf.FilterMaskIdHigh = 0X0000;
CAN1_FilerConf.FilterMaskIdLow = 0X0000;
CAN1_FilerConf.FilterFIFOAssignment = CAN_FILTER_FIFO0;
CAN1_FilerConf.FilterNumber = 0;
CAN1_FilerConf.FilterMode = CAN_FILTERMODE_IDMASK;
CAN1_FilerConf.FilterScale = CAN_FILTERSCALE_32BIT;
CAN1_FilerConf.FilterActivation = ENABLE;
CAN1_FilerConf.BankNumber = 14;
if (HAL_CAN_ConfigFilter(&CAN1_Handler, &CAN1_FilerConf) != HAL_OK)
return 2;
return 0;
}
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_CAN1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_Initure.Pin = GPIO_PIN_11 | GPIO_PIN_12;
GPIO_Initure.Mode = GPIO_MODE_AF_PP;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_FAST;
GPIO_Initure.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
#if CAN1_RX0_INT_ENABLE
__HAL_CAN_ENABLE_IT(&CAN1_Handler, CAN_IT_FMP0);
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 2);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
#endif
}
#if CAN1_RX0_INT_ENABLE
void CAN1_RX0_IRQHandler(void)
{
HAL_CAN_IRQHandler(&CAN1_Handler);
}
#endif
u8 CAN1_Send_Msg(u8 *msg, u8 len)
{
u16 i = 0;
CAN1_Handler.pTxMsg->StdId = 0X12;
CAN1_Handler.pTxMsg->ExtId = 0x12;
CAN1_Handler.pTxMsg->IDE = CAN_ID_STD;
CAN1_Handler.pTxMsg->RTR = CAN_RTR_DATA;
CAN1_Handler.pTxMsg->DLC = len;
for (i = 0; i < len; i++)
CAN1_Handler.pTxMsg->Data[i] = msg[i];
if (HAL_CAN_Transmit(&CAN1_Handler, 10) != HAL_OK)
return 1;
return 0;
}
u8 CAN1_Receive_Msg(u8 *buf)
{
u32 i;
if (HAL_CAN_Receive(&CAN1_Handler, CAN_FIFO0, 0) != HAL_OK)
return 0;
for (i = 0; i < CAN1_Handler.pRxMsg->DLC; i++)
buf[i] = CAN1_Handler.pRxMsg->Data[i];
return CAN1_Handler.pRxMsg->DLC;
}
这里面只关注CAN初始化函数u8 CAN1_Mode_Init(u32 tsjw, u32 tbs2, u32 tbs1, u16 brp, u32 mode) ,可变参数设置波特率和模式(正常模式和循环模式,我们使用正常模式),波特率计算方法注释中有,在main函数中这样调用: CAN1_Mode_Init(CAN_SJW_1TQ, CAN_BS2_6TQ, CAN_BS1_8TQ, 3, CAN_MODE_LOOPBACK) ,从而设置波特率1000K。
CAN接收和发送我们使用官方demo中的函数。
2. 电机数据定义与通讯函数:
创建motor.h文件(以下没有显示无关代码)
#ifndef __MOTOR
#define __MOTOR
#include "stm32f4xx_hal.h"
#include "mytype.h"
#include "can.h"
typedef struct{
int16_t speed_rpm;
int16_t real_current;
int16_t given_current;
uint8_t hall;
uint16_t angle;
uint16_t last_angle;
uint16_t offset_angle;
int32_t round_cnt;
int32_t total_angle;
u8 buf_idx;
u16 angle_buf[FILTER_BUF_LEN];
u16 fited_angle;
u32 msg_cnt;
}moto_measure_t;
#define motor_num 6
#define CAN_CONTROL
extern moto_measure_t moto_chassis[];
extern moto_measure_t moto_info;
u8 get_moto_measure(moto_measure_t *ptr, CAN_HandleTypeDef* hcan);
u8 set_moto_current(CAN_HandleTypeDef* hcan, s16 SID, s16 iq1, s16 iq2, s16 iq3, s16 iq4);
#endif
这里面定义了一个电机数据的结构体moto_measure_t ,宏定义motor_num 是电机数量,可选宏定义CAN_CONTROL 和PWM_CONTROL 决定是使用CAN控制还是PWM控制。
创建motor.c文件(以下没有显示无关代码)
#include "can.h"
#include "motor.h"
moto_measure_t moto_chassis[motor_num] = {0};
moto_measure_t moto_info;
u8 get_moto_measure(moto_measure_t *ptr, CAN_HandleTypeDef* hcan)
{
if(HAL_CAN_Receive(hcan,CAN_FIFO0,0)!=HAL_OK) return 0;
ptr->last_angle = ptr->angle;
ptr->angle = (uint16_t)(hcan->pRxMsg->Data[0]<<8 | hcan->pRxMsg->Data[1]) ;
ptr->real_current = (int16_t)(hcan->pRxMsg->Data[2]<<8 | hcan->pRxMsg->Data[3]);
ptr->speed_rpm = ptr->real_current;
ptr->given_current = (int16_t)(hcan->pRxMsg->Data[4]<<8 | hcan->pRxMsg->Data[5])/-5;
ptr->hall = hcan->pRxMsg->Data[6];
if(ptr->angle - ptr->last_angle > 4096)
ptr->round_cnt --;
else if (ptr->angle - ptr->last_angle < -4096)
ptr->round_cnt ++;
ptr->total_angle = ptr->round_cnt * 8192 + ptr->angle - ptr->offset_angle;
return hcan->pRxMsg->DLC;
}
u8 set_moto_current(CAN_HandleTypeDef* hcan, s16 SID, s16 iq1, s16 iq2, s16 iq3, s16 iq4){
hcan->pTxMsg->StdId = SID;
hcan->pTxMsg->IDE = CAN_ID_STD;
hcan->pTxMsg->RTR = CAN_RTR_DATA;
hcan->pTxMsg->DLC = 0x08;
hcan->pTxMsg->Data[0] = iq1 >> 8;
hcan->pTxMsg->Data[1] = iq1;
hcan->pTxMsg->Data[2] = iq2 >> 8;
hcan->pTxMsg->Data[3] = iq2;
hcan->pTxMsg->Data[4] = iq3 >> 8;
hcan->pTxMsg->Data[5] = iq3;
hcan->pTxMsg->Data[6] = iq4 >> 8;
hcan->pTxMsg->Data[7] = iq4;
if(HAL_CAN_Transmit(&CAN1_Handler,1000)!=HAL_OK) return 1;
return 0;
}
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *hcan)
{
if (hcan->Instance == CAN1)
{
__HAL_CAN_ENABLE_IT(&CAN1_Handler, CAN_IT_FMP0);
switch(hcan->pRxMsg->StdId){
case 0x201: get_moto_measure(&moto_chassis[0], hcan); break;
case 0x202: get_moto_measure(&moto_chassis[1], hcan); break;
case 0x203: get_moto_measure(&moto_chassis[2], hcan); break;
case 0x204: get_moto_measure(&moto_chassis[3], hcan); break;
default:break;
}
}
}
这里面定义了一个电机数组moto_chassis[motor_num] 与一个电机变量moto_info ,定义了两个函数分别进行CAN报文接收与CAN报文发送。u8 get_moto_measure(moto_measure_t *ptr, CAN_HandleTypeDef* hcan) 将报文信息解析后存放到电机数据结构体中,u8 set_moto_current(CAN_HandleTypeDef* hcan, s16 SID, s16 iq1, s16 iq2, s16 iq3, s16 iq4) 设置四个电机的电流值。
最后重定义了CAN接收中断回调函数void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *hcan) ,每接收到一帧报文都会进入这个回调函数。通过接收报文的ID,分别将报文信息更新到四个电机数据结构体中。这个地方很重要!!!
3. 电机PID计算函数:
创建motor_pid.h文件:
#ifndef __MOTOR_PID_H
#define __MOTOR_PID_H
#include "stm32f4xx_hal.h"
enum
{
LLAST = 0,
LAST = 1,
NOW = 2,
POSITION_PID,
DELTA_PID,
};
typedef struct __pid_t
{
float p;
float i;
float d;
float set[3];
float get[3];
float err[3];
float pout;
float iout;
float dout;
float pos_out;
float last_pos_out;
float delta_u;
float delta_out;
float last_delta_out;
float max_err;
float deadband;
uint32_t pid_mode;
uint32_t MaxOutput;
uint32_t IntegralLimit;
void (*f_param_init)(struct __pid_t *pid,
uint32_t pid_mode,
uint32_t maxOutput,
uint32_t integralLimit,
float p,
float i,
float d);
void (*f_pid_reset)(struct __pid_t *pid, float p, float i, float d);
} pid_t;
void PID_struct_init(
pid_t *pid,
uint32_t mode,
uint32_t maxout,
uint32_t intergral_limit,
float kp,
float ki,
float kd);
float pid_calc(pid_t *pid, float fdb, float ref);
extern pid_t pid_rol;
extern pid_t pid_pit;
extern pid_t pid_yaw;
extern pid_t pid_pit_omg;
extern pid_t pid_yaw_omg;
extern pid_t pid_spd[4];
extern pid_t pid_yaw_alfa;
extern pid_t pid_chassis_angle;
extern pid_t pid_poke;
extern pid_t pid_poke_omg;
extern pid_t pid_imu_tmp;
extern pid_t pid_cali_bby;
extern pid_t pid_cali_bbp;
extern pid_t pid_omg;
extern pid_t pid_pos;
#endif
创建motor_pid.c文件:
#include "motor_pid.h"
#include "mytype.h"
#include <math.h>
#define ABS(x) ((x>0)? (x): (-x))
void abs_limit(float *a, float ABS_MAX){
if(*a > ABS_MAX)
*a = ABS_MAX;
if(*a < -ABS_MAX)
*a = -ABS_MAX;
}
static void pid_param_init(
pid_t *pid,
uint32_t mode,
uint32_t maxout,
uint32_t intergral_limit,
float kp,
float ki,
float kd)
{
pid->IntegralLimit = intergral_limit;
pid->MaxOutput = maxout;
pid->pid_mode = mode;
pid->p = kp;
pid->i = ki;
pid->d = kd;
}
static void pid_reset(pid_t *pid, float kp, float ki, float kd)
{
pid->p = kp;
pid->i = ki;
pid->d = kd;
}
float pid_calc(pid_t* pid, float get, float set){
pid->get[NOW] = get;
pid->set[NOW] = set;
pid->err[NOW] = set - get;
if (pid->max_err != 0 && ABS(pid->err[NOW]) > pid->max_err )
return 0;
if (pid->deadband != 0 && ABS(pid->err[NOW]) < pid->deadband)
return 0;
if(pid->pid_mode == POSITION_PID)
{
pid->pout = pid->p * pid->err[NOW];
pid->iout += pid->i * pid->err[NOW];
pid->dout = pid->d * (pid->err[NOW] - pid->err[LAST] );
abs_limit(&(pid->iout), pid->IntegralLimit);
pid->pos_out = pid->pout + pid->iout + pid->dout;
abs_limit(&(pid->pos_out), pid->MaxOutput);
pid->last_pos_out = pid->pos_out;
}
else if(pid->pid_mode == DELTA_PID)
{
pid->pout = pid->p * (pid->err[NOW] - pid->err[LAST]);
pid->iout = pid->i * pid->err[NOW];
pid->dout = pid->d * (pid->err[NOW] - 2*pid->err[LAST] + pid->err[LLAST]);
abs_limit(&(pid->iout), pid->IntegralLimit);
pid->delta_u = pid->pout + pid->iout + pid->dout;
pid->delta_out = pid->last_delta_out + pid->delta_u;
abs_limit(&(pid->delta_out), pid->MaxOutput);
pid->last_delta_out = pid->delta_out;
}
pid->err[LLAST] = pid->err[LAST];
pid->err[LAST] = pid->err[NOW];
pid->get[LLAST] = pid->get[LAST];
pid->get[LAST] = pid->get[NOW];
pid->set[LLAST] = pid->set[LAST];
pid->set[LAST] = pid->set[NOW];
return pid->pid_mode==POSITION_PID ? pid->pos_out : pid->delta_out;
}
void PID_struct_init(
pid_t* pid,
uint32_t mode,
uint32_t maxout,
uint32_t intergral_limit,
float kp,
float ki,
float kd)
{
pid->f_param_init = pid_param_init;
pid->f_pid_reset = pid_reset;
pid->f_param_init(pid, mode, maxout, intergral_limit, kp, ki, kd);
}
PID的代码就不讲了,请自行阅读。
4. main函数:
好了,现在可以写main函数了。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "can.h"
#include "motor.h"
#include "motor_pid.h"
#include "timer.h"
int main(void)
{
u8 key;
s8 key_cnt;
u8 i;
pid_t pid_speed[motor_num];
float set_speed_temp;
int16_t delta;
int16_t max_speed_change = 1000;
static float set_speed[motor_num];
HAL_Init();
Stm32_Clock_Init(360, 25, 2, 8);
delay_init(180);
uart_init(115200);
LED_Init();
KEY_Init();
CAN1_Mode_Init(CAN_SJW_1TQ, CAN_BS2_6TQ, CAN_BS1_8TQ, 3, CAN_MODE_LOOPBACK);
PWM_Init();
for (i = 0; i < 4; i++)
{
PID_struct_init(&pid_speed[i], POSITION_PID, 16384, 16384, 1.5f, 0.1f, 0.0f);
}
while (!get_moto_measure(&moto_info, &CAN1_Handler))
{
LED0 = ~LED0;
delay_ms(200);
}
while (1)
{
key = KEY_Scan(0);
if (key == KEY0_PRES)
{
key_cnt++;
}
else if (key == KEY1_PRES)
{
key_cnt--;
}
#if defined CAN_CONTROL
if (key_cnt < -16)
key_cnt = -16;
if (key_cnt > 16)
key_cnt = 16;
set_speed[0] = set_speed[1] = set_speed[2] = set_speed[3] = key_cnt * 500;
for (i = 0; i < 5; i++)
{
delta = (int16_t)set_speed[i] - moto_chassis[i].speed_rpm;
if (delta > max_speed_change)
set_speed_temp = (float)(moto_chassis[i].speed_rpm + max_speed_change);
else if (delta < -max_speed_change)
set_speed_temp = (float)(moto_chassis[i].speed_rpm - max_speed_change);
else
set_speed_temp = set_speed[i];
pid_calc(&pid_speed[i], (float)moto_chassis[i].speed_rpm, set_speed_temp);
}
printf("PID out: %f\r\n", pid_speed[0].pos_out);
printf("real speed: %d \r\n", moto_chassis[0].speed_rpm);
printf("set speed: %f \r\n", set_speed[0]);
set_moto_current(&CAN1_Handler, 0x200, (s16)(pid_speed[0].pos_out),
(s16)(pid_speed[1].pos_out),
(s16)(pid_speed[2].pos_out),
(s16)(pid_speed[3].pos_out));
#elif defined PWM_CONTROL
if (key_cnt < 0)
key_cnt = 0;
if (key_cnt > 20)
key_cnt = 20;
set_speed[0] = set_speed[1] = set_speed[2] = set_speed[3] = key_cnt * 100;
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, set_speed[0]);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, set_speed[1]);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_3, set_speed[2]);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_4, set_speed[3]);
#endif
delay_ms(5);
}
}
主函数的代码流程如下,具体代码就不讲了。
五. 官方代码移植-查询接收
除了中断接收的方式,还可以使用查询方式进行CAN报文接收,此时的main函数代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "can.h"
#include "motor.h"
#include "motor_pid.h"
#include "timer.h"
int main(void)
{
u8 key;
s8 key_cnt;
u8 i;
u16 retry;
u8 flag_motor[motor_num];
pid_t pid_speed[motor_num];
float set_speed_temp;
int16_t delta;
int16_t max_speed_change = 1000;
static float set_speed[motor_num];
HAL_Init();
Stm32_Clock_Init(360, 25, 2, 8);
delay_init(180);
uart_init(115200);
LED_Init();
KEY_Init();
CAN1_Mode_Init(CAN_SJW_1TQ, CAN_BS2_6TQ, CAN_BS1_8TQ, 3, CAN_MODE_LOOPBACK);
PWM_Init();
for (i = 0; i < 4; i++)
{
PID_struct_init(&pid_speed[i], POSITION_PID, 16384, 16384, 1.5f, 0.1f, 0.0f);
}
while (!get_moto_measure(&moto_info, &CAN1_Handler))
{
LED0 = ~LED0;
delay_ms(200);
}
while (1)
{
key = KEY_Scan(0);
if (key == KEY0_PRES)
{
key_cnt++;
}
else if (key == KEY1_PRES)
{
key_cnt--;
}
#if defined CAN_CONTROL
if (key_cnt < -16)
key_cnt = -16;
if (key_cnt > 16)
key_cnt = 16;
set_speed[0] = set_speed[1] = set_speed[2] = set_speed[3] = key_cnt * 500;
retry = 0;
flag_motor[0] = flag_motor[1] = flag_motor[2] = flag_motor[3] = 0;
while (retry < 20)
{
get_moto_measure(&moto_info, &CAN1_Handler);
if (CAN1_Handler.pRxMsg->StdId == 0x201)
{
moto_chassis[0] = moto_info;
flag_motor[0] = 1;
}
if (CAN1_Handler.pRxMsg->StdId == 0x202)
{
moto_chassis[1] = moto_info;
flag_motor[1] = 1;
}
if (CAN1_Handler.pRxMsg->StdId == 0x203)
{
moto_chassis[2] = moto_info;
flag_motor[2] = 1;
}
if (CAN1_Handler.pRxMsg->StdId == 0x204)
{
moto_chassis[3] = moto_info;
flag_motor[3] = 1;
}
if (flag_motor[0] && flag_motor[1] && flag_motor[2] && flag_motor[3])
break;
else
retry++;
}
for (i = 0; i < 5; i++)
{
delta = (int16_t)set_speed[i] - moto_chassis[i].speed_rpm;
if (delta > max_speed_change)
set_speed_temp = (float)(moto_chassis[i].speed_rpm + max_speed_change);
else if (delta < -max_speed_change)
set_speed_temp = (float)(moto_chassis[i].speed_rpm - max_speed_change);
else
set_speed_temp = set_speed[i];
pid_calc(&pid_speed[i], (float)moto_chassis[i].speed_rpm, set_speed_temp);
}
printf("PID out: %f\r\n", pid_speed[0].pos_out);
printf("real speed: %d \r\n", moto_chassis[0].speed_rpm);
printf("set speed: %f \r\n", set_speed[0]);
set_moto_current(&CAN1_Handler, 0x200, (s16)(pid_speed[0].pos_out),
(s16)(pid_speed[1].pos_out),
(s16)(pid_speed[2].pos_out),
(s16)(pid_speed[3].pos_out));
#elif defined PWM_CONTROL
if (key_cnt < -10)
key_cnt = -10;
if (key_cnt > 10)
key_cnt = 10;
set_speed[0] = set_speed[1] = set_speed[2] = set_speed[3] = key_cnt * 50;
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, 1500+set_speed[0]);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, 1500+set_speed[1]);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_3, 1500+set_speed[2]);
__HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_4, 1500+set_speed[3]);
#endif
delay_ms(5);
}
}
与前面中断接收的唯一不同就是将读取CAN报文放到了while(1)循环中,在每个控制周期内主动读电机信息。另外注意同时关闭CAN接收中断(在can.h中定义#define CAN1_RX0_INT_ENABLE 0 )
|