使用小白STM32控制大疆3508电机学习CAN通信
小白博主最近在学习3508,以下是自己的学习笔记,欢迎大家给博主指出错误,博主感激不尽!!
大疆的代码运用了操作系统,暂时用不上。所以本文完全没提到。不需要使用的时候把大疆demo里面操作系统注释掉即可。
/*can.c注意!!!!!这是老的hal库!!!博主有个同学因为没发现这个问题改了七遍代码
*/
void MX_CAN1_Init(void)
{
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 5;/*设置分频系数*/
hcan1.Init.Mode = CAN_MODE_NORMAL;/*普通模式,回环模式*/
hcan1.Init.SJW = CAN_SJW_1TQ;/*此处可以决定can的波特率*/
hcan1.Init.BS1 = CAN_BS1_3TQ;
hcan1.Init.BS2 = CAN_BS2_5TQ;/*波特率计算公式=APB1总线HZ/[(TS1+TS2+1)*BRP]*/
hcan1.Init.TTCM = DISABLE;/*非时间触发通信模式*/
hcan1.Init.ABOM = ENABLE;
hcan1.Init.AWUM = DISABLE;
hcan1.Init.NART = DISABLE;
hcan1.Init.RFLM = DISABLE;
hcan1.Init.TXFP = DISABLE;
/*以上均为hal库初始化配置*/
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
}
}
/*首先先对can进行参数初始化设置
/*这个会被HAL_CAN-init调用*/
void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(canHandle->Instance==CAN1)/*用于判断是哪个can口~以免后面配置多个can*/
{
/*开时钟*/
HAL_RCC_CAN1_CLK_ENABLED++;
if(HAL_RCC_CAN1_CLK_ENABLED==1){
__HAL_RCC_CAN1_CLK_ENABLE();
}
/*根据自己使用的板子改引脚*/
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(CAN1_TX_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(CAN1_TX_IRQn);
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
/*中断配置*/
}
}
void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{
if(canHandle->Instance==CAN1)
{
HAL_RCC_CAN1_CLK_ENABLED--;
if(HAL_RCC_CAN1_CLK_ENABLED==0){
__HAL_RCC_CAN1_CLK_DISABLE();
}
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);
/* Peripheral interrupt Deinit*/
HAL_NVIC_DisableIRQ(CAN1_TX_IRQn);
HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn);
}
以上,初始化的配置就结束啦!博主现在只会最最基础的,接下来是滤波器的配置
博主用的是FIFO0
简单地说它就是一个控制接收发送的邮箱
/*接下来我们看到can滤波器的配置*/
void my_can_filter_init_recv_all(CAN_HandleTypeDef* _hcan)
{
CAN_FilterConfTypeDef CAN_FilterConfigStructure;
static CanTxMsgTypeDef Tx1Message;
static CanRxMsgTypeDef Rx1Message;
CAN_FilterConfigStructure.FilterNumber = 0;
CAN_FilterConfigStructure.FilterMode = CAN_FILTERMODE_IDMASK;
CAN_FilterConfigStructure.FilterScale = CAN_FILTERSCALE_32BIT;
CAN_FilterConfigStructure.FilterIdHigh = 0x0000;/*32位的ID值*/
CAN_FilterConfigStructure.FilterIdLow = 0x0000;
CAN_FilterConfigStructure.FilterMaskIdHigh = 0x0000;/*32位的MASK值*/
CAN_FilterConfigStructure.FilterMaskIdLow = 0x0000;
CAN_FilterConfigStructure.FilterFIFOAssignment = CAN_FilterFIFO0;
CAN_FilterConfigStructure.BankNumber = 14;//can1(0-13)和can2(14-27)分别得到一半的filter
CAN_FilterConfigStructure.FilterActivation = ENABLE;
if(HAL_CAN_ConfigFilter(_hcan, &CAN_FilterConfigStructure) != HAL_OK)
{
//err_deadloop();
}
//滤波器初始化
CAN_FilterConfigStructure.FilterNumber = 14;
if(HAL_CAN_ConfigFilter(_hcan, &CAN_FilterConfigStructure) != HAL_OK)
{
//err_deadloop();
}
if(_hcan == &hcan1){
_hcan->pTxMsg = &Tx1Message;
/*注意这两个结构体所指向的ptxmsg和prxmsg!!!后面会经常用到*/
_hcan->pRxMsg = &Rx1Message;
}
}
关于滤波器的解释,以下取自正点原子:(就是上面所说的id值)
举个简单的例子,我们设置过滤器组 0 工作在:1 个 32 为位过滤器-标识符屏蔽模式,然 后设置 CAN_F0R1=0XFFFF0000,CAN_F0R2=0XFF00FF00。其中存放到 CAN_F0R1的值就是 期望收到的 ID,即我们希望收到的映像(STID+EXTID+IDE+RTR)最好是:0XFFFF0000。 而 0XFF00FF00 就是设置我们需要必须关心的 ID,表示收到的映像,其位[31:24]和位[15:8]这 16 个位的必须和 CAN_F0R1 中对应的位一模一样,而另外的 16 个位则不关心,可以一样,也 可以不一样,都认为是正确的 ID,即收到的映像必须是 0XFFxx00xx,才算是正确的(x 表示 不关心)。这里需要注意的是标识符选择位 IDE 和帧类型 RTR 需要一致
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* _hcan)
{
__HAL_CAN_ENABLE_IT(&hcan1, CAN_IT_FMP0);
switch(_hcan->pRxMsg->StdId){
/*这里涉及到电调的调节*/
case CAN_3510Moto2_ID:/*这个是电调2*/
{
static u8 i;
i = _hcan->pRxMsg->StdId - CAN_3510Moto1_ID;
/*i其实就是对应的电调值*/
moto_chassis[i].msg_cnt++ <= 50 ? get_moto_offset(&moto_chassis[i],_hcan) :get_moto_measure(&moto_chassis[i], _hcan);
/*这里应该是等待电流值稳定再调用get——moto——measure函数*/
get_moto_measure(&moto_info, _hcan);
//get_moto_measure(&moto_chassis[i], _hcan);
}
break;
}
/*再次使能去解决智能收到一次id的问题*/
__HAL_CAN_ENABLE_IT(&hcan1, CAN_IT_FMP0);
}
void get_moto_measure(moto_measure_t *ptr, CAN_HandleTypeDef* hcan)
{
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;
}
通过以上的代码,可以获取到电机现在的各种值
其中ptr就是这个结构体
typedef struct{
int16_t speed_rpm;
int16_t real_current;
int16_t given_current;
uint8_t hall;
uint16_t angle; //abs angle range:[0,8191]
uint16_t last_angle; //abs angle range:[0,8191]
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;
以下是用于设定电机的电流值通过can发送过去的东西,这里用到了四个电流参数形成了一个pid环
关于这里的stdId值,这是0x202对应的就是第二个电调。
要改变这里的电调值来控制不同的电调
void set_moto_current(CAN_HandleTypeDef* hcan, s16 iq1, s16 iq2, s16 iq3, s16 iq4){
hcan->pTxMsg->StdId = 0x202;
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;
HAL_CAN_Transmit(hcan, 1000);
}
static HAL_StatusTypeDef CAN_Receive_IT(CAN_HandleTypeDef* hcan, uint8_t FIFONumber)
{
/* Get the Id */
hcan->pRxMsg->IDE = (uint8_t)0x04U & hcan->Instance->sFIFOMailBox[FIFONumber].RIR;
if (hcan->pRxMsg->IDE == CAN_ID_STD)
{
hcan->pRxMsg->StdId = (uint32_t)0x000007FFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RIR >> 21U);
}
else
{
hcan->pRxMsg->ExtId = (uint32_t)0x1FFFFFFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RIR >> 3U);
}
hcan->pRxMsg->RTR = (uint8_t)0x02U & hcan->Instance->sFIFOMailBox[FIFONumber].RIR;
/* Get the DLC */
hcan->pRxMsg->DLC = (uint8_t)0x0FU & hcan->Instance->sFIFOMailBox[FIFONumber].RDTR;
/* Get the FMI */
hcan->pRxMsg->FMI = (uint8_t)0xFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RDTR >> 8U);
/* Get the data field */
hcan->pRxMsg->Data[0U] = (uint8_t)0xFFU & hcan->Instance->sFIFOMailBox[FIFONumber].RDLR;
hcan->pRxMsg->Data[1U] = (uint8_t)0xFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RDLR >> 8U);
hcan->pRxMsg->Data[2U] = (uint8_t)0xFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RDLR >> 16U);
hcan->pRxMsg->Data[3U] = (uint8_t)0xFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RDLR >> 24U);
hcan->pRxMsg->Data[4U] = (uint8_t)0xFFU & hcan->Instance->sFIFOMailBox[FIFONumber].RDHR;
hcan->pRxMsg->Data[5U] = (uint8_t)0xFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RDHR >> 8U);
hcan->pRxMsg->Data[6U] = (uint8_t)0xFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RDHR >> 16U);
hcan->pRxMsg->Data[7U] = (uint8_t)0xFFU & (hcan->Instance->sFIFOMailBox[FIFONumber].RDHR >> 24U);
/* Release the FIFO */
/* Release FIFO0 */
if (FIFONumber == CAN_FIFO0)
{
__HAL_CAN_FIFO_RELEASE(hcan, CAN_FIFO0);
/* Disable FIFO 0 message pending Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_FMP0);
}
/* Release FIFO1 */
else /* FIFONumber == CAN_FIFO1 */
{
__HAL_CAN_FIFO_RELEASE(hcan, CAN_FIFO1);
/* Disable FIFO 1 message pending Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_FMP1);
}
if(hcan->State == HAL_CAN_STATE_BUSY_RX)
{
/* Disable Error warning Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_EWG);
/* Disable Error passive Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_EPV);
/* Disable Bus-off Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_BOF);
/* Disable Last error code Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_LEC);
/* Disable Error Interrupt */
__HAL_CAN_DISABLE_IT(hcan, CAN_IT_ERR);
}
if(hcan->State == HAL_CAN_STATE_BUSY_TX_RX)
{
/* Disable CAN state */
hcan->State = HAL_CAN_STATE_BUSY_TX;
}
else
{
/* Change CAN state */
hcan->State = HAL_CAN_STATE_READY;
}
/* Receive complete callback */
HAL_CAN_RxCpltCallback(hcan);/*注意这里!!!!!!!调用了我们上面定义的callback函数*/
/* Return function status */
return HAL_OK;
}
HAL库里面的函数。
在HAL_CAN_recieve_IT里面没有调用callback函数,但是在can_recieve_it里面调用了。
方法2:正常的方法,不需自己在hal库里面开启中断:
使用公共处理函数
void CAN1_RX0_IRQHandler(void)
{
/* USER CODE BEGIN CAN1_RX0_IRQn 0 */
/* USER CODE END CAN1_RX0_IRQn 0 */
HAL_CAN_IRQHandler(&hcan1);
/* USER CODE BEGIN CAN1_RX0_IRQn 1 */
/* USER CODE END CAN1_RX0_IRQn 1 */
}
后面就是讲解pid的部分,pid算法用于稳定电机输出的电流值和转动的速度。简单来说就是把参数丢进去它能帮你计算,让你的电机稳定的转动。
static void pid_param_init(
pid_t *pid,
uint32_t mode,
uint32_t maxout,
uint32_t intergral_limit,
float kp,
float ki,
float kd)
kp,ki,kd三个参数分别对应比例常数,微分常数,积分常数
float pid_calc(pid_t* pid, float get, float set){
pid->get[NOW] = get;
pid->set[NOW] = set;
pid->err[NOW] = set - get; //set - measure
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) //位置式p
{
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; //update last time
}
else if(pid->pid_mode == DELTA_PID)//增量式P
{
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; //update last time
}
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;
//
}
通过pid计算前后值,设置两个-结构体用于存储pid速度和电流值,用于pid_calc中调用
pid_t pid_speed[4]; //电机速度PID环
pid_t pid_position[4]; //电机电流PID环
moto_measure_t moto_chassis[4] = {0};
上面这个值会被HAL_CAN_RxCpltCallback调用获取电机的参数值,再传入pid中用于计算,最后得到设置电机的值
需要注意的几点:
1.不同板子代码的移植,比如这个时钟在stm32f429中就无法正常使用(暂时没去找原因)。
2.canreceive_it和hal_receive_it的区别
3.电调id值的设定
4.新老hal库的冲突
作者:99errors100warnings