完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
一. 大疆M3508电机
大疆M3508电机可以从RM官网下载很多相关资料,这里不做过多赘述。关于电机配套电调使用的时候,重点是如下两张图: 第一张图是电调接收报文的格式,即如果要发送数据给1号到4号电调,控制电机输出电流,从而控制电机转速时,需要按照图一中的内容将CAN数据帧的ID设置为0x200(如果要控制5号到8号电调时应为0x1FF),数据域中的8Byte数据按照电调1到4的高八位和低八位顺序装填,帧格式和DLC也按照标准内容进行设置,最后进行数据发送即可。 第二张图是接收电调所发送的数据时,首先根据接收的ID判断究竟接收到的是哪个电调发送来的数据,手册中规定1号电调ID为0x201,2号为0x202依次类推。判断完数据来源之后就可以按照数据格式进行解码,通过高八位和低八位拼接方式,得到转子机械角度,转速,转矩电流,电机温度等数据。 二. CAN通讯协议 CAN(Controller Area Network) 是控制器域网的简称,由德国BOACH公司开发并最终成为国际标准的一种通讯协议,其总线由两根线构成并将各个设备一起挂载在总线上。CAN协议由于仅靠两根线实现,所以该协议较UART等其他协议而言相对复杂。 大疆M3508电机的驱动便采用CAN协议进行通讯,一个完整的数据帧由以下部分组成: 仲裁场: 每个挂载在CAN总线上的CAN都有自己的ID,每当一个设备发送一帧数据的时候,总线其他设备会检查这个ID是否是自己需要接收的数据对象,如果是则接受本帧数据,否则忽略。仲裁场便是用来存放CAN的ID的位置,CAN的ID分为标准ID和拓展ID,标准ID长度为11位,如果设备过多,标准ID不够用的情况下可以使用拓展ID,拓展ID长度有29位。 控制场: 控制场中的DLC规定了本帧数据的长度,而数据场内的数据大小为8Byte,即8个8位数据,CAN总线的一个数据帧中所需要传输的有效数据实际上就是这8Byte。 三. PID控制 PID控制是一种常用的控制算法,其基本思想是利用期望值和实际值的误差作为控制量决定最终的输出,关于PID有很多写的很好的资料,这里只简单说一下自己对于PID的理解和在此处的应用: PID由三项,即Proportional(比例项),Integration(积分项),Derivative(微分项)组成。 **比例项:**控制器比例项输出值和误差值保持线性关系,误差放大倍数与输出值放大倍数一致。在控制电机运转过程中,可以很简单地依靠比例项实现控制器的基本功能,但往往存在静差过大引起系统震荡的问题,导致电机不能平稳的运转,通俗来说就是不够丝滑,表现出变化不连续的振荡。 **积分项:**控制器积分项输出值和误差值的积分呈线性关系,利用误差值累计进行控制,积分增益过大容易引起积分超调的现象。 **微分项:**微分项的大小和输出值的变化成正相关,微分项可以对系统的改变作出反应,对系统的短期改变有很大帮助。 这里有一个表供参考: 主要谈一下为什么在这里使用PID控制:当电机运转过程中,如果没有任何负载的话,电机的转速便是所提供的转速。然而,电机工作必然是要提供负载的,此时电机在负载的作用下,其转速必然会降低,若不提供任何外界干扰的情况下,假如想要提供2000rpm的转速,当添加负载后,电机的实际转速可能仅有1800rpm,这样便无法满足实际要求。因此,通过对于当前电机速度的测量,建立一个反馈系统,与所要求的转速进行比较,将误差作为反馈量给原输入进行调节,这样便可迅速将电机提升到所要求的转速,同时具备较强的抗干扰能力。 四. CubeMX配置 1.外部晶振设定: 2.时钟树配置: 3.CAN1总线协议配置: 4.GPIO口的配置: 这里是最容易出问题的地方,第一次控制M3508电机的时候在这里卡了好久,一定要注意默认的CAN_RX和CAN_TX接口并非实际电路中的CAN接口,这里需要查找电路图才能发现实际的CAN1对应的GPIO口实际上是PD0和PD1,因此应该在此处选定为PD0和PD1,否则电机是无法控制转动的。 按照上述配置CAN1,基本上便可控制M3508的转动了 部分代码和说明 motor.h: /** ***************************************(C) COPYRIGHT 2021 CSS*************************************** * @file motor.h * @brief this file contains the common defines and functions prototypes for * the motor.c driver * @note * @Version V1.0.0 * @Date Feb-15-2021 ***************************************(C) COPYRIGHT 2021 CSS*************************************** */ #ifndef __MOTOR_H__ #define __MOTOR_H__ #include "can.h" typedef enum { CAN_CHASSIS_ALL_ID = 0x200, CAN_AUXILIARY_ALL_ID = 0x1FF, motor1 = 0x201, motor2 = 0x202, motor3 = 0x203, motor4 = 0x204, motor6 = 0x205, }can_msg_id; typedef struct { uint16_t ecd; int16_t speed_rpm; int16_t given_current; uint8_t temperate; int16_t last_ecd; }motor_measure_t; void can_filter_init(void); void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); void CAN_cmd_auxiliary(int16_t M5, int16_t M6); void CAN_cmd_chassis(int16_t M1, int16_t M2, int16_t M3, int16_t M4); #endif motor.c: /** ***************************************(C) COPYRIGHT 2021 CSS*************************************** * @file motor.c * @brief this file contains M3508 processing function * @note * @Version V1.0.0 * @Date Feb-15-2021 ***************************************(C) COPYRIGHT 2021 CSS*************************************** */ #include "motor.h" #include "main.h" motor_measure_t motor_chassis[7]; uint8_t chassis_can_send_data[8]; CAN_TxHeaderTypeDef chassis_tx_message; //motor data read #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)[2]<<8|(data)[3]); (ptr)->ecd=(uint16_t)((data)[4]<<8|(data)[5]); (ptr)->ecd=(data)[6]; } /** * @brief ·µ»Øµ×Å̵ç»ú 3508µç»úÊý¾ÝÖ¸Õë * @param[in] i: µç»ú±àºÅ,·¶Î§[0,3] * @retval µç»úÊý¾ÝÖ¸Õë */ motor_measure_t* get_chassis_motor_measure_point(uint8_t i) { return &motor_chassis[(i & 0x03)]; } /** * @brief send control current of motor (0x201, 0x202, 0x203, 0x204) * @param[in] motor1: (0x201) 3508 motor control current, range [-16384,16384] * @param[in] motor2: (0x202) 3508 motor control current, range [-16384,16384] * @param[in] motor3: (0x203) 3508 motor control current, range [-16384,16384] * @param[in] motor4: (0x204) 3508 motor control current, range [-16384,16384] * @retval none */ void CAN_cmd_chassis(int16_t M1, int16_t M2, int16_t M3, int16_t M4) { uint32_t send_mail_box; chassis_tx_message.StdId=CAN_CHASSIS_ALL_ID; chassis_tx_message.IDE=CAN_ID_STD; chassis_tx_message.RTR=CAN_RTR_DATA; chassis_tx_message.DLC=0x08; chassis_can_send_data[0]=M1>>8; chassis_can_send_data[1]=M1; chassis_can_send_data[2]=M2>>8; chassis_can_send_data[3]=M2; chassis_can_send_data[4]=M3>>8; chassis_can_send_data[5]=M3; chassis_can_send_data[6]=M4>>8; chassis_can_send_data[7]=M4; HAL_CAN_AddTxMessage(&hcan1,&chassis_tx_message,chassis_can_send_data,&send_mail_box); } /** * @brief send control current of motor * @param[in] motor5: 3508 motor control current, range [-16384,16384] * @param[in] motor6: 3508 motor control current, range [-16384,16384] * @retval none */ void CAN_cmd_auxiliary(int16_t M5, int16_t M6) { uint32_t send_mail_box; chassis_tx_message.StdId=CAN_AUXILIARY_ALL_ID; chassis_tx_message.IDE=CAN_ID_STD; chassis_tx_message.RTR=CAN_RTR_DATA; chassis_tx_message.DLC=0x08; chassis_can_send_data[0]=M5>>8; chassis_can_send_data[1]=M5; chassis_can_send_data[2]=M6>>8; chassis_can_send_data[3]=M6; HAL_CAN_AddTxMessage(&hcan1,&chassis_tx_message,chassis_can_send_data,&send_mail_box); } /** * @brief hal CAN fifo call back, receive motor data * @param[in] hcan, the point to CAN handle * @retval none */ void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef*hcan) { CAN_RxHeaderTypeDef rx_header; uint8_t rx_data[8]; HAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0,&rx_header,rx_data); switch(rx_header.StdId) { case motor1: case motor2: case motor3: case motor4: case motor6: { static uint8_t i = 0; i = rx_header.StdId - motor1; get_motor_measure(&motor_chassis,rx_data); break; } default: { break; } } } /** * @brief filter function * @retval none */ void can_filter_init(void) { CAN_FilterTypeDef can_filter_st; can_filter_st.FilterActivation = ENABLE; can_filter_st.FilterMode = CAN_FILTERMODE_IDMASK; can_filter_st.FilterScale = CAN_FILTERSCALE_32BIT; can_filter_st.FilterIdHigh = 0x0000; can_filter_st.FilterIdLow = 0x0000; can_filter_st.FilterMaskIdHigh = 0x0000; can_filter_st.FilterMaskIdLow = 0x0000; can_filter_st.FilterBank = 0; can_filter_st.FilterFIFOAssignment = CAN_RX_FIFO0; HAL_CAN_ConfigFilter(&hcan1, &can_filter_st); HAL_CAN_Start(&hcan1); HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); } pid.h: /** ***************************************(C) COPYRIGHT 2021 CSS*************************************** * @file pid.h * @brief this file contains the common defines and functions prototypes for * the pid.c driver * @note * @Version V1.0.0 * @Date Feb-15-2021 ***************************************(C) COPYRIGHT 2021 CSS*************************************** */ #ifndef __PID_H__ #define __PID_H__ #include "stdint.h" typedef float fp32; enum PID_MODE { PID_POSITION = 0, PID_DELTA }; typedef struct//pid { uint8_t mode; //PID Èý²ÎÊý fp32 Kp; fp32 Ki; fp32 Kd; fp32 max_out; //×î´óÊä³ö fp32 max_iout; //×î´ó»ý·ÖÊä³ö fp32 set; fp32 fdb; fp32 out; fp32 Pout; fp32 Iout; fp32 Dout; fp32 Dbuf[3]; //΢·ÖÏî 0×îР1ÉÏÒ»´Î 2ÉÏÉÏ´Î fp32 error[3]; //Îó²îÏî 0×îР1ÉÏÒ»´Î 2ÉÏÉÏ´Î }PidTypeDef; fp32 PID_Calc(PidTypeDef *pid,fp32 ref,fp32 set); void PID_init(PidTypeDef *pid,uint8_t mode,const fp32 PID[3],fp32 max_out,fp32 max_iout); #endif pid.c: /** ***************************************(C) COPYRIGHT 2021 CSS*************************************** * @file pid.c * @brief this file contains pid processing function * @note * @Version V1.0.0 * @Date Feb-15-2021 ***************************************(C) COPYRIGHT 2021 CSS*************************************** */ #include "pid.h" #include "main.h" #define LimitMax(input, max) { if (input > max) { input = max; } else if (input < -max) { input = -max; } } void PID_init(PidTypeDef *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; } fp32 PID_Calc(PidTypeDef *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; } 在主函数main.c中调用即可 /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** ** This notice applies to any and all portions of this file * that are not between comment pairs USER CODE BEGIN and * USER CODE END. Other portions of this file, whether * inserted by the user or by software development tools * are owned by their respective copyright owners. * * COPYRIGHT(c) 2020 STMicroelectronics * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of STMicroelectronics nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "pid.h" //pid control #include "motor.h" //motor control /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ uint16_t set_speed; PidTypeDef motor_pid; fp32 PID[3]={3,0.1,0}; motor_measure_t* motor_data; //PID_motor param /* Private variables ---------------------------------------------------------*/ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* Private function prototypes -----------------------------------------------*/ /* motor control */ motor_measure_t* get_chassis_motor_measure_point(uint8_t i); void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan); void CAN_cmd_chassis(int16_t M1, int16_t M2, int16_t M3, int16_t M4); /* pid control */ fp32 PID_Calc(PidTypeDef *pid,fp32 ref,fp32 set); /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_CAN1_Init(); /* motor initial */ can_filter_init(); PID_init(&motor_pid,PID_POSITION,PID,16000,2000); motor_data = get_chassis_motor_measure_point(0); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ for(set_speed = 0; set_speed < 2000; set_speed += 200) { PID_Calc(&motor_pid,motor_data->speed_rpm,set_speed); CAN_cmd_chassis(-motor_pid.out,motor_pid.out,motor_pid.out,motor_pid.out); HAL_Delay(100); } /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /**Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); /**Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 6; RCC_OscInitStruct.PLL.PLLN = 168; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 4; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /**Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ while(1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %drn", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ ———————————————— 六. 结果与测试 七. 一些说明 这篇博客也过了好久一直没有写完,这些工作已经隔了大半年,因此代码可能会有一些地方出现小错误,但因为之前已经实现过,基本思路上不会有太大问题,主函数main.c内部可以根据自己的需要做一些调整,函数有引用其他人的,也有自己改写过的,基本上应该是可以直接调用,如果有问题也希望可以一起交流探讨。 因为之前自己在做RM电机的时候卡过很多地方,尤其是GPIO口那个问题,当时根本没有意识到默认的GPIO口其实是不对的,重点还是要结合原理图看吧。希望其他人在看到类似的问题后可以少走一些弯路,如果有新的见解也可以提出来一起讨论。 |
|
|
|
只有小组成员才能发言,加入小组>>
2435 浏览 0 评论
9100 浏览 4 评论
36773 浏览 19 评论
5026 浏览 0 评论
24745 浏览 34 评论
1531浏览 2评论
1747浏览 1评论
2196浏览 1评论
1556浏览 0评论
526浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 05:25 , Processed in 1.415479 second(s), Total 77, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号