diff --git a/.DS_Store b/.DS_Store index 99e2101..e391af5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/bsp/can.c b/bsp/can.c index 71451fc..c2fe26b 100644 --- a/bsp/can.c +++ b/bsp/can.c @@ -37,16 +37,21 @@ static osMutexId_t queue_mutex = NULL; static void (*CAN_Callback[BSP_CAN_NUM][BSP_CAN_CB_NUM])(void); static bool inited = false; static BSP_CAN_IdParser_t id_parser = NULL; /* ID解析器 */ +static BSP_CAN_TxQueue_t tx_queues[BSP_CAN_NUM]; /* 每个CAN的发送队列 */ /* Private function prototypes ---------------------------------------------- */ static BSP_CAN_t CAN_Get(CAN_HandleTypeDef *hcan); static osMessageQueueId_t BSP_CAN_FindQueue(BSP_CAN_t can, uint32_t can_id); static int8_t BSP_CAN_CreateIdQueue(BSP_CAN_t can, uint32_t can_id, uint8_t queue_size); -static int8_t BSP_CAN_DeleteIdQueue(BSP_CAN_t can, uint32_t can_id); static void BSP_CAN_RxFifo0Callback(void); static void BSP_CAN_RxFifo1Callback(void); +static void BSP_CAN_TxCompleteCallback(void); static BSP_CAN_FrameType_t BSP_CAN_GetFrameType(CAN_RxHeaderTypeDef *header); static uint32_t BSP_CAN_DefaultIdParser(uint32_t original_id, BSP_CAN_FrameType_t frame_type); +static void BSP_CAN_TxQueueInit(BSP_CAN_t can); +static bool BSP_CAN_TxQueuePush(BSP_CAN_t can, BSP_CAN_TxMessage_t *msg); +static bool BSP_CAN_TxQueuePop(BSP_CAN_t can, BSP_CAN_TxMessage_t *msg); +static bool BSP_CAN_TxQueueIsEmpty(BSP_CAN_t can); /* Private functions -------------------------------------------------------- */ /* USER FUNCTION BEGIN */ @@ -118,29 +123,7 @@ static int8_t BSP_CAN_CreateIdQueue(BSP_CAN_t can, uint32_t can_id, uint8_t queu return BSP_OK; } -/** - * @brief 删除指定CAN ID的消息队列 - * @note 内部函数,已包含互斥锁保护 - */ -static int8_t BSP_CAN_DeleteIdQueue(BSP_CAN_t can, uint32_t can_id) { - if (osMutexAcquire(queue_mutex, CAN_QUEUE_MUTEX_TIMEOUT) != osOK) { - return BSP_ERR_TIMEOUT; - } - BSP_CAN_QueueNode_t **current = &queue_list; - while (*current != NULL) { - if ((*current)->can == can && (*current)->can_id == can_id) { - BSP_CAN_QueueNode_t *to_delete = *current; - *current = (*current)->next; - osMessageQueueDelete(to_delete->queue); - BSP_Free(to_delete); - osMutexRelease(queue_mutex); - return BSP_OK; - } - current = &(*current)->next; - } - osMutexRelease(queue_mutex); - return BSP_ERR; // 未找到 -} + /** * @brief 获取帧类型 */ @@ -160,6 +143,106 @@ static uint32_t BSP_CAN_DefaultIdParser(uint32_t original_id, BSP_CAN_FrameType_ return original_id; } +/** + * @brief 初始化发送队列 + */ +static void BSP_CAN_TxQueueInit(BSP_CAN_t can) { + if (can >= BSP_CAN_NUM) return; + + tx_queues[can].head = 0; + tx_queues[can].tail = 0; +} + +/** + * @brief 向发送队列添加消息(无锁) + */ +static bool BSP_CAN_TxQueuePush(BSP_CAN_t can, BSP_CAN_TxMessage_t *msg) { + if (can >= BSP_CAN_NUM || msg == NULL) return false; + + BSP_CAN_TxQueue_t *queue = &tx_queues[can]; + uint32_t next_head = (queue->head + 1) % BSP_CAN_TX_QUEUE_SIZE; + + // 队列满 + if (next_head == queue->tail) { + return false; + } + + // 复制消息 + queue->buffer[queue->head] = *msg; + + // 更新头指针(原子操作) + queue->head = next_head; + + return true; +} + + +/** + * @brief 从发送队列取出消息(无锁) + */ +static bool BSP_CAN_TxQueuePop(BSP_CAN_t can, BSP_CAN_TxMessage_t *msg) { + if (can >= BSP_CAN_NUM || msg == NULL) return false; + + BSP_CAN_TxQueue_t *queue = &tx_queues[can]; + + // 队列空 + if (queue->head == queue->tail) { + return false; + } + + // 复制消息 + *msg = queue->buffer[queue->tail]; + + // 更新尾指针(原子操作) + queue->tail = (queue->tail + 1) % BSP_CAN_TX_QUEUE_SIZE; + + return true; +} + +/** + * @brief 检查发送队列是否为空 + */ +static bool BSP_CAN_TxQueueIsEmpty(BSP_CAN_t can) { + if (can >= BSP_CAN_NUM) return true; + + return tx_queues[can].head == tx_queues[can].tail; +} + +/** + * @brief 处理所有CAN实例的发送队列 + */ +static void BSP_CAN_TxCompleteCallback(void) { + // 处理所有CAN实例的发送队列 + for (int i = 0; i < BSP_CAN_NUM; i++) { + BSP_CAN_t can = (BSP_CAN_t)i; + CAN_HandleTypeDef *hcan = BSP_CAN_GetHandle(can); + if (hcan == NULL) continue; + + BSP_CAN_TxMessage_t msg; + uint32_t mailbox; + + // 尝试发送队列中的消息 + while (!BSP_CAN_TxQueueIsEmpty(can)) { + // 检查是否有空闲邮箱 + if (HAL_CAN_GetTxMailboxesFreeLevel(hcan) == 0) { + break; // 没有空闲邮箱,等待下次中断 + } + + // 从队列中取出消息 + if (!BSP_CAN_TxQueuePop(can, &msg)) { + break; + } + + // 发送消息 + if (HAL_CAN_AddTxMessage(hcan, &msg.header, msg.data, &mailbox) != HAL_OK) { + // 发送失败,消息已经从队列中移除,直接丢弃 + break; + } + } + } +} + + /** * @brief FIFO0接收处理函数 */ @@ -344,7 +427,12 @@ int8_t BSP_CAN_Init(void) { // 清零回调函数数组 memset(CAN_Callback, 0, sizeof(CAN_Callback)); - + + // 初始化发送队列 + for (int i = 0; i < BSP_CAN_NUM; i++) { + BSP_CAN_TxQueueInit((BSP_CAN_t)i); + } + // 初始化ID解析器为默认解析器 id_parser = BSP_CAN_DefaultIdParser; @@ -360,39 +448,6 @@ int8_t BSP_CAN_Init(void) { return BSP_OK; } -int8_t BSP_CAN_DeInit(void) { - if (!inited) { - return BSP_ERR; - } - - // 删除所有队列 - if (osMutexAcquire(queue_mutex, CAN_QUEUE_MUTEX_TIMEOUT) == osOK) { - BSP_CAN_QueueNode_t *current = queue_list; - while (current != NULL) { - BSP_CAN_QueueNode_t *next = current->next; - osMessageQueueDelete(current->queue); - BSP_Free(current); - current = next; - } - queue_list = NULL; - osMutexRelease(queue_mutex); - } - - // 删除互斥锁 - if (queue_mutex != NULL) { - osMutexDelete(queue_mutex); - queue_mutex = NULL; - } - - // 清零回调函数数组 - memset(CAN_Callback, 0, sizeof(CAN_Callback)); - - // 重置ID解析器 - id_parser = NULL; - - inited = false; - return BSP_OK; -} CAN_HandleTypeDef *BSP_CAN_GetHandle(BSP_CAN_t can) { if (can >= BSP_CAN_NUM) { @@ -445,44 +500,58 @@ int8_t BSP_CAN_Transmit(BSP_CAN_t can, BSP_CAN_Format_t format, return BSP_ERR_NULL; } - CAN_TxHeaderTypeDef header = {0}; - uint32_t mailbox; + // 准备发送消息 + BSP_CAN_TxMessage_t tx_msg = {0}; switch (format) { case BSP_CAN_FORMAT_STD_DATA: - header.StdId = id; - header.IDE = CAN_ID_STD; - header.RTR = CAN_RTR_DATA; + tx_msg.header.StdId = id; + tx_msg.header.IDE = CAN_ID_STD; + tx_msg.header.RTR = CAN_RTR_DATA; break; case BSP_CAN_FORMAT_EXT_DATA: - header.ExtId = id; - header.IDE = CAN_ID_EXT; - header.RTR = CAN_RTR_DATA; + tx_msg.header.ExtId = id; + tx_msg.header.IDE = CAN_ID_EXT; + tx_msg.header.RTR = CAN_RTR_DATA; break; case BSP_CAN_FORMAT_STD_REMOTE: - header.StdId = id; - header.IDE = CAN_ID_STD; - header.RTR = CAN_RTR_REMOTE; + tx_msg.header.StdId = id; + tx_msg.header.IDE = CAN_ID_STD; + tx_msg.header.RTR = CAN_RTR_REMOTE; break; case BSP_CAN_FORMAT_EXT_REMOTE: - header.ExtId = id; - header.IDE = CAN_ID_EXT; - header.RTR = CAN_RTR_REMOTE; + tx_msg.header.ExtId = id; + tx_msg.header.IDE = CAN_ID_EXT; + tx_msg.header.RTR = CAN_RTR_REMOTE; break; default: return BSP_ERR; } - header.DLC = dlc; - header.TransmitGlobalTime = DISABLE; + tx_msg.header.DLC = dlc; + tx_msg.header.TransmitGlobalTime = DISABLE; - HAL_StatusTypeDef result = HAL_CAN_AddTxMessage(hcan, &header, data, &mailbox); - - if (result != HAL_OK) { - return BSP_ERR; + // 复制数据 + if (data != NULL && dlc > 0) { + memcpy(tx_msg.data, data, dlc); } - return BSP_OK; + // 尝试直接发送到邮箱 + uint32_t mailbox; + if (HAL_CAN_GetTxMailboxesFreeLevel(hcan) > 0) { + HAL_StatusTypeDef result = HAL_CAN_AddTxMessage(hcan, &tx_msg.header, tx_msg.data, &mailbox); + if (result == HAL_OK) { + return BSP_OK; // 发送成功 + } + } + + // 邮箱满,尝试放入队列 + if (BSP_CAN_TxQueuePush(can, &tx_msg)) { + return BSP_OK; // 成功放入队列 + } + + // 队列也满,丢弃数据 + return BSP_ERR; // 数据丢弃 } int8_t BSP_CAN_TransmitStdDataFrame(BSP_CAN_t can, BSP_CAN_StdDataFrame_t *frame) { @@ -514,12 +583,6 @@ int8_t BSP_CAN_RegisterId(BSP_CAN_t can, uint32_t can_id, uint8_t queue_size) { return BSP_CAN_CreateIdQueue(can, can_id, queue_size); } -int8_t BSP_CAN_UnregisterIdQueue(BSP_CAN_t can, uint32_t can_id) { - if (!inited) { - return BSP_ERR_INITED; - } - return BSP_CAN_DeleteIdQueue(can, can_id); -} int8_t BSP_CAN_GetMessage(BSP_CAN_t can, uint32_t can_id, BSP_CAN_Message_t *msg, uint32_t timeout) { if (!inited) { @@ -586,15 +649,6 @@ int8_t BSP_CAN_RegisterIdParser(BSP_CAN_IdParser_t parser) { return BSP_OK; } -int8_t BSP_CAN_UnregisterIdParser(void) { - if (!inited) { - return BSP_ERR_INITED; - } - - id_parser = BSP_CAN_DefaultIdParser; - return BSP_OK; -} - uint32_t BSP_CAN_ParseId(uint32_t original_id, BSP_CAN_FrameType_t frame_type) { if (id_parser != NULL) { return id_parser(original_id, frame_type); @@ -602,43 +656,4 @@ uint32_t BSP_CAN_ParseId(uint32_t original_id, BSP_CAN_FrameType_t frame_type) { return BSP_CAN_DefaultIdParser(original_id, frame_type); } -int8_t BSP_CAN_WaitTxMailboxEmpty(BSP_CAN_t can, uint32_t timeout) { - if (!inited) { - return BSP_ERR_INITED; - } - if (can >= BSP_CAN_NUM) { - return BSP_ERR; - } - - CAN_HandleTypeDef *hcan = BSP_CAN_GetHandle(can); - if (hcan == NULL) { - return BSP_ERR_NULL; - } - - uint32_t start_time = HAL_GetTick(); - - // 如果超时时间为0,立即检查并返回 - if (timeout == 0) { - uint32_t free_level = HAL_CAN_GetTxMailboxesFreeLevel(hcan); - return (free_level > 0) ? BSP_OK : BSP_ERR_TIMEOUT; - } - - // 等待至少有一个邮箱空闲 - while (true) { - uint32_t free_level = HAL_CAN_GetTxMailboxesFreeLevel(hcan); - if (free_level > 0) { - return BSP_OK; - } - - // 检查超时 - if (timeout != BSP_CAN_TIMEOUT_FOREVER) { - uint32_t elapsed = HAL_GetTick() - start_time; - if (elapsed >= timeout) { - return BSP_ERR_TIMEOUT; - } - } - - // 短暂延时,避免占用过多CPU - osDelay(1); - } -} + diff --git a/bsp/can.h b/bsp/can.h index f987abe..e6b5f71 100644 --- a/bsp/can.h +++ b/bsp/can.h @@ -21,6 +21,7 @@ extern "C" { #define BSP_CAN_DEFAULT_QUEUE_SIZE 10 #define BSP_CAN_TIMEOUT_IMMEDIATE 0 #define BSP_CAN_TIMEOUT_FOREVER osWaitForever +#define BSP_CAN_TX_QUEUE_SIZE 32 /* 发送队列大小 */ /* USER DEFINE BEGIN */ @@ -29,7 +30,8 @@ extern "C" { /* Exported macro ----------------------------------------------------------- */ /* Exported types ----------------------------------------------------------- */ typedef enum { -/* AUTO GENERATED BSP_CAN_NAME */ + BSP_CAN_1, + BSP_CAN_2, BSP_CAN_NUM, BSP_CAN_ERR, } BSP_CAN_t; @@ -101,6 +103,19 @@ typedef struct { /* ID解析回调函数类型 */ typedef uint32_t (*BSP_CAN_IdParser_t)(uint32_t original_id, BSP_CAN_FrameType_t frame_type); +/* CAN发送消息结构体 */ +typedef struct { + CAN_TxHeaderTypeDef header; /* 发送头 */ + uint8_t data[BSP_CAN_MAX_DLC]; /* 数据 */ +} BSP_CAN_TxMessage_t; + +/* 无锁环形队列结构体 */ +typedef struct { + BSP_CAN_TxMessage_t buffer[BSP_CAN_TX_QUEUE_SIZE]; /* 缓冲区 */ + volatile uint32_t head; /* 队列头 */ + volatile uint32_t tail; /* 队列尾 */ +} BSP_CAN_TxQueue_t; + /* USER STRUCT BEGIN */ /* USER STRUCT END */ @@ -113,12 +128,6 @@ typedef uint32_t (*BSP_CAN_IdParser_t)(uint32_t original_id, BSP_CAN_FrameType_t */ int8_t BSP_CAN_Init(void); -/** - * @brief 反初始化 CAN 模块 - * @return BSP_OK 成功,其他值失败 - */ -int8_t BSP_CAN_DeInit(void); - /** * @brief 获取 CAN 句柄 * @param can CAN 枚举 @@ -172,13 +181,20 @@ int8_t BSP_CAN_TransmitExtDataFrame(BSP_CAN_t can, BSP_CAN_ExtDataFrame_t *frame */ int8_t BSP_CAN_TransmitRemoteFrame(BSP_CAN_t can, BSP_CAN_RemoteFrame_t *frame); + /** - * @brief 等待CAN发送邮箱空闲 + * @brief 获取发送队列中待发送消息数量 + * @param can CAN 枚举 + * @return 队列中消息数量,-1表示错误 + */ +int32_t BSP_CAN_GetTxQueueCount(BSP_CAN_t can); + +/** + * @brief 清空发送队列 * @param can CAN 枚举 - * @param timeout 超时时间(毫秒),0为立即返回,osWaitForever为永久等待 * @return BSP_OK 成功,其他值失败 */ -int8_t BSP_CAN_WaitTxMailboxEmpty(BSP_CAN_t can, uint32_t timeout); +int8_t BSP_CAN_FlushTxQueue(BSP_CAN_t can); /** * @brief 注册 CAN ID 接收队列 @@ -189,13 +205,7 @@ int8_t BSP_CAN_WaitTxMailboxEmpty(BSP_CAN_t can, uint32_t timeout); */ int8_t BSP_CAN_RegisterId(BSP_CAN_t can, uint32_t can_id, uint8_t queue_size); -/** - * @brief 注销 CAN ID 接收队列 - * @param can CAN 枚举 - * @param can_id 解析后的CAN ID - * @return BSP_OK 成功,其他值失败 - */ -int8_t BSP_CAN_UnregisterIdQueue(BSP_CAN_t can, uint32_t can_id); + /** * @brief 获取 CAN 消息 @@ -230,11 +240,6 @@ int8_t BSP_CAN_FlushQueue(BSP_CAN_t can, uint32_t can_id); */ int8_t BSP_CAN_RegisterIdParser(BSP_CAN_IdParser_t parser); -/** - * @brief 注销ID解析器 - * @return BSP_OK 成功,其他值失败 - */ -int8_t BSP_CAN_UnregisterIdParser(void); /** * @brief 解析CAN ID diff --git a/bsp/describe.csv b/bsp/describe.csv index fd64d14..5206a52 100644 --- a/bsp/describe.csv +++ b/bsp/describe.csv @@ -1,5 +1,5 @@ uart,请开启uart的dma和中断 -can,请开启can中断,使用函数前请确保can已经初始化。 +can,请开启can中断,使用函数前请确保can已经初始化。一定要开启can发送中断!!! gpio,会自动读取cubemx中配置为gpio的引脚,并自动区分输入输出和中断。 spi,请开启spi的dma和中断 i2c,要求开始spi中断 diff --git a/component/user_math.c b/component/user_math.c index 6bc958d..5e0b0c4 100644 --- a/component/user_math.c +++ b/component/user_math.c @@ -9,33 +9,33 @@ /* USER INCLUDE END */ inline float InvSqrt(float x) { - // #if 0 +//#if 0 /* Fast inverse square-root */ /* See: http://en.wikipedia.org/wiki/Fast_inverse_square_root */ - float halfx = 0.5f * x; - float y = x; - long i = *(long *)&y; - i = 0x5f3759df - (i >> 1); - y = *(float *)&i; - y = y * (1.5f - (halfx * y * y)); - y = y * (1.5f - (halfx * y * y)); - return y; - // #else - // return 1.0f / sqrtf(x); - // #endif + float halfx = 0.5f * x; + float y = x; + long i = *(long*)&y; + i = 0x5f3759df - (i>>1); + y = *(float*)&i; + y = y * (1.5f - (halfx * y * y)); + y = y * (1.5f - (halfx * y * y)); + return y; +//#else +// return 1.0f / sqrtf(x); +//#endif } inline float AbsClip(float in, float limit) { return (in < -limit) ? -limit : ((in > limit) ? limit : in); } -float fAbs(float in) { return (in > 0) ? in : -in; } +float fAbs(float in){ + return (in > 0) ? in : -in; +} inline void Clip(float *origin, float min, float max) { - if (*origin > max) - *origin = max; - if (*origin < min) - *origin = min; + if (*origin > max) *origin = max; + if (*origin < min) *origin = min; } inline float Sign(float in) { return (in > 0) ? 1.0f : 0.0f; } @@ -48,9 +48,8 @@ inline float Sign(float in) { return (in > 0) ? 1.0f : 0.0f; } inline void ResetMoveVector(MoveVector_t *mv) { memset(mv, 0, sizeof(*mv)); } /** - * \brief - *计算循环值的误差,适用于设定值与反馈值均在(x,y)范围内循环的情况,range应设定为y-x - *例如:(-M_PI,M_PI)range=M_2PI;(0,M_2PI)range=M_2PI;(a,a+b)range=b; + * \brief 计算循环值的误差,适用于设定值与反馈值均在(x,y)范围内循环的情况,range应设定为y-x + * 例如:(-M_PI,M_PI)range=M_2PI;(0,M_2PI)range=M_2PI;(a,a+b)range=b; * \param sp 设定值 * \param fb 反馈值 * \param range 被操作的值变化范围,正数时起效 @@ -70,8 +69,7 @@ inline float CircleError(float sp, float fb, float range) { } /** - * \brief 循环加法,适用于被操作的值在(x,y)范围内循环的情况,range应设定为y-x - * 例如:(-M_PI,M_PI)range=M_2PI;(0,M_2PI)range=M_2PI;(a,a+b)range=b; + * \brief 循环加法,适用于被操作的值在(0,range)范围内循环的情况 * \param origin 被操作的值 * \param delta 变化量 * \param range 被操作的值变化范围,正数时起效 @@ -103,20 +101,14 @@ inline void CircleReverse(float *origin) { *origin = -(*origin) + M_2PI; } * @return 摩擦轮转速 */ inline float CalculateRpm(float bullet_speed, float fric_radius, bool is17mm) { - if (bullet_speed == 0.0f) - return 0.f; + if (bullet_speed == 0.0f) return 0.f; if (is17mm) { - if (bullet_speed == 15.0f) - return 4670.f; - if (bullet_speed == 18.0f) - return 5200.f; - if (bullet_speed == 30.0f) - return 7350.f; + if (bullet_speed == 15.0f) return 4670.f; + if (bullet_speed == 18.0f) return 5200.f; + if (bullet_speed == 30.0f) return 7350.f; } else { - if (bullet_speed == 10.0f) - return 4450.f; - if (bullet_speed == 16.0f) - return 5800.f; + if (bullet_speed == 10.0f) return 4450.f; + if (bullet_speed == 16.0f) return 5800.f; } /* 不为裁判系统设定值时,计算转速 */ diff --git a/component/user_math.h b/component/user_math.h index 405edf3..6e61ca2 100644 --- a/component/user_math.h +++ b/component/user_math.h @@ -21,6 +21,10 @@ extern "C" { #define M_DEG2RAD_MULT (0.01745329251f) #define M_RAD2DEG_MULT (57.2957795131f) +#ifndef M_PI_2 +#define M_PI_2 1.57079632679f +#endif + #ifndef M_PI #define M_PI 3.14159265358979323846f #endif @@ -83,20 +87,16 @@ void ResetMoveVector(MoveVector_t *mv); /** * \brief 计算循环值的误差,适用于设定值与反馈值均在(x,y)范围内循环的情况,range应设定为y-x - *例如:(-M_PI,M_PI)range=M_2PI;(0,M_2PI)range=M_2PI;(a,a+b)range=b; - * + * 例如:(-M_PI,M_PI)range=M_2PI;(0,M_2PI)range=M_2PI;(a,a+b)range=b; * \param sp 设定值 * \param fb 反馈值 * \param range 被操作的值变化范围,正数时起效 - * * \return 函数运行结果 */ float CircleError(float sp, float fb, float range); /** - * \brief 循环加法,适用于被操作的值在(x,y)范围内循环的情况,range应设定为y-x - * 例如:(-M_PI,M_PI)range=M_2PI;(0,M_2PI)range=M_2PI;(a,a+b)range=b; - * + * \brief 循环加法,适用于被操作的值在(0,range)范围内循环的情况 * \param origin 被操作的值 * \param delta 变化量 * \param range 被操作的值变化范围,正数时起效 diff --git a/config.csv b/config.csv index 84b7c39..32290e1 100644 --- a/config.csv +++ b/config.csv @@ -1,4 +1,4 @@ bsp,can,dwt,gpio,i2c,mm,spi,uart,pwm,time component,ahrs,capacity,cmd,crc8,crc16,error_detect,filter,FreeRTOS_CLI,limiter,mixer,pid,ui,user_math -device,dr16,bmi088,ist8310,motor,motor_rm,motor_vesc,motor_lk,motor_lz,motor_odrive,dm_imu,servo,buzzer,led,ws2812,vofa,ops9 -module, \ No newline at end of file +device,dr16,bmi088,ist8310,motor,motor_rm,motor_dm,motor_vesc,motor_lk,motor_lz,motor_odrive,dm_imu,rc_can,servo,buzzer,led,ws2812,vofa,ops9 +module,config, \ No newline at end of file diff --git a/device/dm_imu.c b/device/dm_imu.c index e05c1de..e5380d9 100644 --- a/device/dm_imu.c +++ b/device/dm_imu.c @@ -166,7 +166,6 @@ int8_t DM_IMU_Request(DM_IMU_t *imu, DM_IMU_RID_t rid) { .dlc = 4, }; memcpy(frame.data, tx_data, 4); - BSP_CAN_WaitTxMailboxEmpty(imu->param.can, 1); // 等待发送邮箱空闲 int8_t result = BSP_CAN_TransmitStdDataFrame(imu->param.can, &frame); return (result == BSP_OK) ? DEVICE_OK : DEVICE_ERR; } @@ -253,8 +252,9 @@ int8_t DM_IMU_AutoUpdateAll(DM_IMU_t *imu){ count++; if (count >= 4) { count = 0; // 重置计数器 + return DEVICE_OK; } - return DEVICE_OK; + return DEVICE_ERR; } /** diff --git a/device/dr16.h b/device/dr16.h index d537ffa..03fa526 100644 --- a/device/dr16.h +++ b/device/dr16.h @@ -94,7 +94,6 @@ typedef struct { uint16_t res; /* 保留,未启用 */ } DR16_Data_t; - typedef struct { DEVICE_Header_t header; DR16_RawData_t raw_data; diff --git a/device/motor_dm.c b/device/motor_dm.c new file mode 100644 index 0000000..a77656f --- /dev/null +++ b/device/motor_dm.c @@ -0,0 +1,496 @@ +#define MOTOR_DM_FLOAT_TO_INT_SIGNED(x, x_min, x_max, bits) \ + ((int32_t)roundf(((x) / ((x_max) - (x_min))) * (1 << (bits)) + (1 << ((bits) - 1)))) + +#define MOTOR_DM_INT_TO_FLOAT_SIGNED(x_int, x_min, x_max, bits) \ + (((float)((int32_t)(x_int) - (1 << ((bits) - 1))) * ((x_max) - (x_min)) / (float)(1 << (bits)))) +/* Includes ----------------------------------------------------------------- */ +#include "device/motor_dm.h" +#include "bsp/mm.h" +#include "bsp/time.h" +#include "component/user_math.h" +#include "string.h" +#include "math.h" + +/* Private constants -------------------------------------------------------- */ +/* DM电机数据映射范围 */ +#define DM_P_MIN (-12.56637f) +#define DM_P_MAX (12.56637f) +#define DM_V_MIN (-30.0f) +#define DM_V_MAX (30.0f) +#define DM_T_MIN (-12.0f) +#define DM_T_MAX (12.0f) +#define DM_KP_MIN (0.0f) +#define DM_KP_MAX (500.0f) +#define DM_KD_MIN (0.0f) +#define DM_KD_MAX (5.0f) + +/* CAN ID偏移量 */ +#define DM_CAN_ID_OFFSET_POS_VEL 0x100 +#define DM_CAN_ID_OFFSET_VEL 0x200 + +/* Private macro ------------------------------------------------------------ */ +#define FLOAT_TO_UINT(x, x_min, x_max, bits) \ + (uint32_t)((x - x_min) * ((1 << bits) - 1) / (x_max - x_min)) + +#define UINT_TO_FLOAT(x_int, x_min, x_max, bits) \ + ((float)(x_int) * (x_max - x_min) / ((1 << bits) - 1) + x_min) + + +/* Private variables -------------------------------------------------------- */ +static MOTOR_DM_CANManager_t *can_managers[BSP_CAN_NUM] = {NULL}; + +static int float_to_uint(float x_float, float x_min, float x_max, int bits) +{ + /* Converts a float to an unsigned int, given range and number of bits */ + float span = x_max - x_min; + float offset = x_min; + return (int) ((x_float-offset)*((float)((1<motor.feedback.rotor_abs_angle = uint_to_float(p_int, DM_P_MIN, DM_P_MAX, 16); // (-12.5,12.5) + uint16_t v_int=(data[3]<<4)|(data[4]>>4); + motor->motor.feedback.rotor_speed = uint_to_float(v_int, DM_V_MIN, DM_V_MAX, 12); // (-30.0,30.0) + uint16_t t_int=((data[4]&0xF)<<8)|data[5]; + motor->motor.feedback.torque_current = uint_to_float(t_int, DM_T_MIN, DM_T_MAX, 12); // (-12.0,12.0) + motor->motor.feedback.temp = (float)(data[6]); + + while (motor->motor.feedback.rotor_abs_angle < 0) { + motor->motor.feedback.rotor_abs_angle += M_2PI; + } + while (motor->motor.feedback.rotor_abs_angle >= M_2PI) { + motor->motor.feedback.rotor_abs_angle -= M_2PI; + } + + if (motor->param.reverse) { + motor->motor.feedback.rotor_abs_angle = M_2PI - motor->motor.feedback.rotor_abs_angle; + motor->motor.feedback.rotor_speed = -motor->motor.feedback.rotor_speed; + motor->motor.feedback.torque_current = -motor->motor.feedback.torque_current; + } + return DEVICE_OK; +} + +/** + * @brief 发送MIT模式控制命令 + * @param motor 电机实例 + * @param output MIT控制参数 + * @return 发送结果 + */ +static int8_t MOTOR_DM_SendMITCmd(MOTOR_DM_t *motor, MOTOR_MIT_Output_t *output) { + if (motor == NULL || output == NULL) { + return DEVICE_ERR_NULL; + } + + uint8_t data[8]; + uint16_t pos_tmp,vel_tmp,kp_tmp,kd_tmp,tor_tmp; + uint16_t id = motor->param.can_id; + + pos_tmp = float_to_uint(output->angle, DM_P_MIN , DM_P_MAX, 16); + vel_tmp = float_to_uint(output->velocity, DM_V_MIN , DM_V_MAX, 12); + kp_tmp = float_to_uint(output->kp, DM_KP_MIN, DM_KP_MAX, 12); + kd_tmp = float_to_uint(output->kd, DM_KD_MIN, DM_KD_MAX, 12); + tor_tmp = float_to_uint(output->torque, DM_T_MIN , DM_T_MAX, 12); + + /* 打包数据 */ + data[0] = (pos_tmp >> 8); + data[1] = pos_tmp; + data[2] = (vel_tmp >> 4); + data[3] = ((vel_tmp&0xF)<<4)|(kp_tmp>>8); + data[4] = kp_tmp; + data[5] = (kd_tmp >> 4); + data[6] = ((kd_tmp&0xF)<<4)|(tor_tmp>>8); + data[7] = tor_tmp; + + /* 发送CAN消息 */ + BSP_CAN_StdDataFrame_t frame; + frame.id = motor->param.can_id; + frame.dlc = 8; + memcpy(frame.data, data, 8); + + + return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; +} + +/** + * @brief 发送位置速度模式控制命令 + * @param motor 电机实例 + * @param pos 目标位置 + * @param vel 目标速度 + * @return 发送结果 + */ +static int8_t MOTOR_DM_SendPosVelCmd(MOTOR_DM_t *motor, float pos, float vel) { + if (motor == NULL) { + return DEVICE_ERR_NULL; + } + + uint8_t data[8] = {0}; + + + /* 直接发送浮点数数据 */ + memcpy(&data[0], &pos, 4); // 位置,低位在前 + memcpy(&data[4], &vel, 4); // 速度,低位在前 + + /* 发送CAN消息,ID为原ID+0x100 */ + uint32_t can_id = DM_CAN_ID_OFFSET_POS_VEL + motor->param.can_id; + BSP_CAN_StdDataFrame_t frame; + frame.id = can_id; + frame.dlc = 8; + memcpy(frame.data, data, 8); + + return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; +} + +/** + * @brief 发送速度模式控制命令 + * @param motor 电机实例 + * @param vel 目标速度 + * @return 发送结果 + */ +static int8_t MOTOR_DM_SendVelCmd(MOTOR_DM_t *motor, float vel) { + if (motor == NULL) { + return DEVICE_ERR_NULL; + } + + uint8_t data[4] = {0}; + + /* 直接发送浮点数数据 */ + memcpy(&data[0], &vel, 4); // 速度,低位在前 + + /* 发送CAN消息,ID为原ID+0x200 */ + uint32_t can_id = DM_CAN_ID_OFFSET_VEL + motor->param.can_id; + BSP_CAN_StdDataFrame_t frame; + frame.id = can_id; + frame.dlc = 4; + memcpy(frame.data, data, 4); + + return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; +} + +/** + * @brief 获取指定CAN总线的管理器 + * @param can CAN总线 + * @return CAN管理器指针 + */ +static MOTOR_DM_CANManager_t* MOTOR_DM_GetCANManager(BSP_CAN_t can) { + if (can >= BSP_CAN_NUM) { + return NULL; + } + + return can_managers[can]; +} + +/** + * @brief 创建CAN管理器 + * @param can CAN总线 + * @return 创建结果 + */ +static int8_t MOTOR_DM_CreateCANManager(BSP_CAN_t can) { + if (can >= BSP_CAN_NUM) return DEVICE_ERR; + if (can_managers[can] != NULL) return DEVICE_OK; + + can_managers[can] = (MOTOR_DM_CANManager_t*)BSP_Malloc(sizeof(MOTOR_DM_CANManager_t)); + if (can_managers[can] == NULL) return DEVICE_ERR; + + memset(can_managers[can], 0, sizeof(MOTOR_DM_CANManager_t)); + can_managers[can]->can = can; + return DEVICE_OK; +} + +/* Exported functions ------------------------------------------------------- */ + +/** + * @brief 注册一个DM电机 + * @param param 电机参数 + * @return 注册结果 + */ +int8_t MOTOR_DM_Register(MOTOR_DM_Param_t *param) { + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + /* 创建CAN管理器 */ + if (MOTOR_DM_CreateCANManager(param->can) != DEVICE_OK) { + return DEVICE_ERR; + } + + /* 获取CAN管理器 */ + MOTOR_DM_CANManager_t *manager = MOTOR_DM_GetCANManager(param->can); + if (manager == NULL) { + return DEVICE_ERR; + } + + /* 检查是否已注册 */ + for (int i = 0; i < manager->motor_count; i++) { + if (manager->motors[i] && manager->motors[i]->param.master_id == param->master_id) { + return DEVICE_ERR_INITED; + } + } + + /* 检查是否已达到最大数量 */ + if (manager->motor_count >= MOTOR_DM_MAX_MOTORS) { + return DEVICE_ERR; + } + + /* 分配内存 */ + MOTOR_DM_t *motor = (MOTOR_DM_t *)BSP_Malloc(sizeof(MOTOR_DM_t)); + if (motor == NULL) { + return DEVICE_ERR; + } + + /* 初始化电机 */ + memset(motor, 0, sizeof(MOTOR_DM_t)); + memcpy(&motor->param, param, sizeof(MOTOR_DM_Param_t)); + motor->motor.header.online = false; + motor->motor.reverse = param->reverse; + + /* 注册CAN接收ID - DM电机使用Master ID接收反馈 */ + uint16_t feedback_id = param->master_id; + if (BSP_CAN_RegisterId(param->can, feedback_id, 3) != BSP_OK) { + BSP_Free(motor); + return DEVICE_ERR; + } + + /* 添加到管理器 */ + manager->motors[manager->motor_count] = motor; + manager->motor_count++; + + return DEVICE_OK; +} + +/** + * @brief 更新指定电机数据 + * @param param 电机参数 + * @return 更新结果 + */ +int8_t MOTOR_DM_Update(MOTOR_DM_Param_t *param) { + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + MOTOR_DM_CANManager_t *manager = MOTOR_DM_GetCANManager(param->can); + if (manager == NULL) { + return DEVICE_ERR_NO_DEV; + } + + /* 查找电机 */ + MOTOR_DM_t *motor = NULL; + for (int i = 0; i < manager->motor_count; i++) { + if (manager->motors[i] && manager->motors[i]->param.master_id == param->master_id) { + motor = manager->motors[i]; + break; + } + } + + if (motor == NULL) { + return DEVICE_ERR_NO_DEV; + } + + /* 主动接收CAN消息 */ + uint16_t feedback_id = param->master_id; + BSP_CAN_Message_t rx_msg; + if (BSP_CAN_GetMessage(param->can, feedback_id, &rx_msg, BSP_CAN_TIMEOUT_IMMEDIATE) != BSP_OK) { + uint64_t now_time = BSP_TIME_Get(); + if (now_time - motor->motor.header.last_online_time > 100000) { // 100ms超时,单位微秒 + motor->motor.header.online = false; + } + return DEVICE_ERR; + } + + motor->motor.header.online = true; + motor->motor.header.last_online_time = BSP_TIME_Get(); + MOTOR_DM_ParseFeedbackFrame(motor, rx_msg.data); + + return DEVICE_OK; +} + +/** + * @brief 更新所有电机数据 + * @return 更新结果 + */ +int8_t MOTOR_DM_UpdateAll(void) { + int8_t ret = DEVICE_OK; + for (int can = 0; can < BSP_CAN_NUM; can++) { + MOTOR_DM_CANManager_t *manager = MOTOR_DM_GetCANManager((BSP_CAN_t)can); + if (manager == NULL) continue; + + for (int i = 0; i < manager->motor_count; i++) { + MOTOR_DM_t *motor = manager->motors[i]; + if (motor != NULL) { + if (MOTOR_DM_Update(&motor->param) != DEVICE_OK) { + ret = DEVICE_ERR; + } + } + } + } + return ret; +} + +/** + * @brief MIT模式控制 + * @param param 电机参数 + * @param output MIT控制参数 + * @return 控制结果 + */ +int8_t MOTOR_DM_MITCtrl(MOTOR_DM_Param_t *param, MOTOR_MIT_Output_t *output) { + if (param == NULL || output == NULL) { + return DEVICE_ERR_NULL; + } + + MOTOR_DM_t *motor = MOTOR_DM_GetMotor(param); + if (motor == NULL) { + return DEVICE_ERR_NO_DEV; + } + + return MOTOR_DM_SendMITCmd(motor, output); +} + +/** + * @brief 位置速度模式控制 + * @param param 电机参数 + * @param target_pos 目标位置 + * @param target_vel 目标速度 + * @return 控制结果 + */ +int8_t MOTOR_DM_PosVelCtrl(MOTOR_DM_Param_t *param, float target_pos, float target_vel) { + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + MOTOR_DM_t *motor = MOTOR_DM_GetMotor(param); + if (motor == NULL) { + return DEVICE_ERR_NO_DEV; + } + + return MOTOR_DM_SendPosVelCmd(motor, target_pos, target_vel); +} + +/** + * @brief 速度模式控制 + * @param param 电机参数 + * @param target_vel 目标速度 + * @return 控制结果 + */ +int8_t MOTOR_DM_VelCtrl(MOTOR_DM_Param_t *param, float target_vel) { + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + MOTOR_DM_t *motor = MOTOR_DM_GetMotor(param); + if (motor == NULL) { + return DEVICE_ERR_NO_DEV; + } + + return MOTOR_DM_SendVelCmd(motor, target_vel); +} + +/** + * @brief 获取指定电机的实例指针 + * @param param 电机参数 + * @return 电机实例指针 + */ +MOTOR_DM_t* MOTOR_DM_GetMotor(MOTOR_DM_Param_t *param) { + if (param == NULL) { + return NULL; + } + + MOTOR_DM_CANManager_t *manager = MOTOR_DM_GetCANManager(param->can); + if (manager == NULL) { + return NULL; + } + + /* 查找对应的电机 */ + for (int i = 0; i < manager->motor_count; i++) { + MOTOR_DM_t *motor = manager->motors[i]; + if (motor && motor->param.can == param->can && + motor->param.master_id == param->master_id) { + return motor; + } + } + + return NULL; +} + + +int8_t MOTOR_DM_Enable(MOTOR_DM_Param_t *param){ + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + MOTOR_DM_t *motor = MOTOR_DM_GetMotor(param); + if (motor == NULL) { + return DEVICE_ERR_NO_DEV; + } + + BSP_CAN_StdDataFrame_t frame; + frame.id = motor->param.can_id; + frame.dlc = 8; + frame.data[0] = 0XFF; + frame.data[1] = 0xFF; + frame.data[2] = 0xFF; + frame.data[3] = 0xFF; + frame.data[4] = 0xFF; + frame.data[5] = 0xFF; + frame.data[6] = 0xFF; + frame.data[7] = 0xFC; + + return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; +} + +/** + * @brief 使电机松弛(设置输出为0) + * @param param 电机参数 + * @return 操作结果 + */ +int8_t MOTOR_DM_Relax(MOTOR_DM_Param_t *param) { + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + MOTOR_MIT_Output_t output = {0}; + return MOTOR_DM_MITCtrl(param, &output); +} + +/** + * @brief 使电机离线(设置在线状态为false) + * @param param 电机参数 + * @return 操作结果 + */ +int8_t MOTOR_DM_Offine(MOTOR_DM_Param_t *param) { + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + MOTOR_DM_t *motor = MOTOR_DM_GetMotor(param); + if (motor == NULL) { + return DEVICE_ERR_NO_DEV; + } + + motor->motor.header.online = false; + return DEVICE_OK; +} diff --git a/device/motor_dm.h b/device/motor_dm.h new file mode 100644 index 0000000..20e91d4 --- /dev/null +++ b/device/motor_dm.h @@ -0,0 +1,98 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ----------------------------------------------------------------- */ +#include "device/device.h" +#include "device/motor.h" +#include "bsp/can.h" + +/* Exported constants ------------------------------------------------------- */ +#define MOTOR_DM_MAX_MOTORS 32 + +/* Exported macro ----------------------------------------------------------- */ +/* Exported types ----------------------------------------------------------- */ +typedef enum { + MOTOR_DM_J4310, +} MOTOR_DM_Module_t; + +/*每个电机需要的参数*/ +typedef struct { + BSP_CAN_t can; + uint16_t master_id; /* 主站ID,用于发送控制命令 */ + uint16_t can_id; /* 反馈ID,用于接收电机反馈 */ + MOTOR_DM_Module_t module; + bool reverse; +} MOTOR_DM_Param_t; + +/*电机实例*/ +typedef struct{ + MOTOR_DM_Param_t param; + MOTOR_t motor; +} MOTOR_DM_t; + +/*CAN管理器,管理一个CAN总线上所有的电机*/ +typedef struct { + BSP_CAN_t can; + MOTOR_DM_t *motors[MOTOR_DM_MAX_MOTORS]; + uint8_t motor_count; +} MOTOR_DM_CANManager_t; + +/* Exported functions prototypes -------------------------------------------- */ + +/** + * @brief 注册一个LK电机 + * @param param 电机参数 + * @return + */ +int8_t MOTOR_DM_Register(MOTOR_DM_Param_t *param); + +/** + * @brief 更新指定电机数据 + * @param param 电机参数 + * @return + */ +int8_t MOTOR_DM_Update(MOTOR_DM_Param_t *param); + +/** + * @brief 更新所有电机数据 + * @return + */ +int8_t MOTOR_DM_UpdateAll(void); + +int8_t MOTOR_DM_MITCtrl(MOTOR_DM_Param_t *param, MOTOR_MIT_Output_t *output); + +int8_t MOTOR_DM_PosVelCtrl(MOTOR_DM_Param_t *param, float target_pos, float target_vel); + +int8_t MOTOR_DM_VelCtrl(MOTOR_DM_Param_t *param, float target_vel); + +/** + * @brief 获取指定电机的实例指针 + * @param param 电机参数 + * @return + */ +MOTOR_DM_t* MOTOR_DM_GetMotor(MOTOR_DM_Param_t *param); + +int8_t MOTOR_DM_Enable(MOTOR_DM_Param_t *param); + +/** + * @brief 使电机松弛(设置输出为0) + * @param param + * @return + */ +int8_t MOTOR_DM_Relax(MOTOR_DM_Param_t *param); + +/** + * @brief 使电机离线(设置在线状态为false) + * @param param + * @return + */ +int8_t MOTOR_DM_Offine(MOTOR_DM_Param_t *param); + + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/device/motor_lk.c b/device/motor_lk.c index c7911d6..c26878e 100644 --- a/device/motor_lk.c +++ b/device/motor_lk.c @@ -253,7 +253,6 @@ int8_t MOTOR_LK_SetOutput(MOTOR_LK_Param_t *param, float value) { tx_frame.data[5] = (uint8_t)((torque_control >> 8) & 0xFF); tx_frame.data[6] = 0x00; tx_frame.data[7] = 0x00; - BSP_CAN_WaitTxMailboxEmpty(param->can, 1); // 等待发送邮箱空闲 return BSP_CAN_TransmitStdDataFrame(param->can, &tx_frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; } @@ -279,7 +278,6 @@ int8_t MOTOR_LK_MotorOn(MOTOR_LK_Param_t *param) { tx_frame.data[5] = 0x00; tx_frame.data[6] = 0x00; tx_frame.data[7] = 0x00; - BSP_CAN_WaitTxMailboxEmpty(param->can, 1); // 等待发送邮箱空闲 return BSP_CAN_TransmitStdDataFrame(param->can, &tx_frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; } @@ -299,7 +297,6 @@ int8_t MOTOR_LK_MotorOff(MOTOR_LK_Param_t *param) { tx_frame.data[5] = 0x00; tx_frame.data[6] = 0x00; tx_frame.data[7] = 0x00; - BSP_CAN_WaitTxMailboxEmpty(param->can, 1); // 等待发送邮箱空闲 return BSP_CAN_TransmitStdDataFrame(param->can, &tx_frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; } diff --git a/device/motor_lz.c b/device/motor_lz.c index eb55f36..7ed7344 100644 --- a/device/motor_lz.c +++ b/device/motor_lz.c @@ -134,7 +134,6 @@ static int8_t MOTOR_LZ_SendExtFrame(BSP_CAN_t can, uint32_t ext_id, uint8_t *dat } else { memset(tx_frame.data, 0, dlc); } - BSP_CAN_WaitTxMailboxEmpty(can, 1); // 等待发送邮箱空闲 return BSP_CAN_TransmitExtDataFrame(can, &tx_frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; } @@ -244,14 +243,6 @@ int8_t MOTOR_LZ_Init(void) { return BSP_CAN_RegisterIdParser(MOTOR_LZ_IdParser) == BSP_OK ? DEVICE_OK : DEVICE_ERR; } -/** - * @brief 反初始化灵足电机驱动系统 - * @return 设备状态码 - */ -int8_t MOTOR_LZ_DeInit(void) { - // 注销ID解析器 - return BSP_CAN_UnregisterIdParser() == BSP_OK ? DEVICE_OK : DEVICE_ERR; -} int8_t MOTOR_LZ_Register(MOTOR_LZ_Param_t *param) { if (param == NULL) return DEVICE_ERR_NULL; @@ -369,7 +360,6 @@ int8_t MOTOR_LZ_MotionControl(MOTOR_LZ_Param_t *param, MOTOR_LZ_MotionParam_t *m uint16_t raw_kd = MOTOR_LZ_FloatToRawPositive(send_param.kd, LZ_KD_MAX); data[6] = (raw_kd >> 8) & 0xFF; data[7] = raw_kd & 0xFF; - BSP_CAN_WaitTxMailboxEmpty(param->can, 1); // 等待发送邮箱空闲 return MOTOR_LZ_SendExtFrame(param->can, ext_id, data, 8); } diff --git a/device/motor_lz.h b/device/motor_lz.h index 76a72ae..d2a8002 100644 --- a/device/motor_lz.h +++ b/device/motor_lz.h @@ -111,12 +111,6 @@ typedef struct { */ int8_t MOTOR_LZ_Init(void); -/** - * @brief 反初始化灵足电机驱动系统 - * @return 设备状态码 - */ -int8_t MOTOR_LZ_DeInit(void); - /** * @brief 注册一个灵足电机 * @param param 电机参数 diff --git a/device/motor_rm.c b/device/motor_rm.c index 1b61374..1e78a47 100644 --- a/device/motor_rm.c +++ b/device/motor_rm.c @@ -74,7 +74,7 @@ static int8_t MOTOR_RM_GetLogicalIndex(uint16_t can_id, MOTOR_RM_Module_t module static float MOTOR_RM_GetRatio(MOTOR_RM_Module_t module) { switch (module) { case MOTOR_M2006: return 36.0f; - case MOTOR_M3508: return 19.0f; + case MOTOR_M3508: return 3591.0f / 187.0f; case MOTOR_GM6020: return 1.0f; default: return 1.0f; } @@ -139,12 +139,12 @@ static void Motor_RM_Decode(MOTOR_RM_t *motor, BSP_CAN_Message_t *msg) { motor->feedback.rotor_speed = rotor_speed; motor->feedback.torque_current = torque_current; } - while (motor->feedback.rotor_abs_angle < 0) { - motor->feedback.rotor_abs_angle += M_2PI; - } - while (motor->feedback.rotor_abs_angle >= M_2PI) { - motor->feedback.rotor_abs_angle -= M_2PI; - } + while (motor->feedback.rotor_abs_angle < 0) { + motor->feedback.rotor_abs_angle += M_2PI; + } + while (motor->feedback.rotor_abs_angle >= M_2PI) { + motor->feedback.rotor_abs_angle -= M_2PI; + } if (motor->motor.reverse) { motor->feedback.rotor_abs_angle = M_2PI - motor->feedback.rotor_abs_angle; motor->feedback.rotor_speed = -motor->feedback.rotor_speed; @@ -291,7 +291,6 @@ int8_t MOTOR_RM_Ctrl(MOTOR_RM_Param_t *param) { default: return DEVICE_ERR; } - BSP_CAN_WaitTxMailboxEmpty(param->can, 1); // 等待发送邮箱空闲 return BSP_CAN_TransmitStdDataFrame(param->can, &tx_frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR; } diff --git a/device/rc_can.c b/device/rc_can.c new file mode 100644 index 0000000..77d9733 --- /dev/null +++ b/device/rc_can.c @@ -0,0 +1,328 @@ +/* Includes ----------------------------------------------------------------- */ +#include "device/rc_can.h" +#include "bsp/time.h" +#include "device/device.h" +/* USER INCLUDE BEGIN */ + +/* USER INCLUDE END */ + +/* Private constants -------------------------------------------------------- */ + +/* USER DEFINE BEGIN */ + +/* USER DEFINE END */ + +/* Private macro ------------------------------------------------------------ */ +/* Private types ------------------------------------------------------------ */ +/* Private variables -------------------------------------------------------- */ + +/* USER VARIABLE BEGIN */ + +/* USER VARIABLE END */ + +/* USER FUNCTION BEGIN */ + +/* USER FUNCTION END */ + +/* Private function prototypes ---------------------------------------------- */ +static int8_t RC_CAN_ValidateParams(const RC_CAN_Param_t *param); +static int8_t RC_CAN_RegisterIds(RC_CAN_t *rc_can); + +/* Exported functions ------------------------------------------------------- */ + +/** + * @brief 初始化RC CAN发送模块 + * @param rc_can RC_CAN结构体指针 + * @param param 初始化参数 + * @return DEVICE_OK 成功,其他值失败 + */ +int8_t RC_CAN_Init(RC_CAN_t *rc_can, RC_CAN_Param_t *param) { + if (rc_can == NULL || param == NULL) { + return DEVICE_ERR_NULL; + } + + // 参数验证 + if (RC_CAN_ValidateParams(param) != DEVICE_OK) { + return DEVICE_ERR; + } + + rc_can->param = *param; + + // 初始化header + rc_can->header.online = false; + rc_can->header.last_online_time = 0; + + // 手动初始化数据结构 + rc_can->data.joy.ch_l_x = 0.0f; + rc_can->data.joy.ch_l_y = 0.0f; + rc_can->data.joy.ch_r_x = 0.0f; + rc_can->data.joy.ch_r_y = 0.0f; + rc_can->data.sw.sw_l = RC_CAN_SW_ERR; + rc_can->data.sw.sw_r = RC_CAN_SW_ERR; + rc_can->data.sw.ch_res = 0.0f; + rc_can->data.mouse.x = 0.0f; + rc_can->data.mouse.y = 0.0f; + rc_can->data.mouse.z = 0.0f; + rc_can->data.mouse.mouse_l = false; + rc_can->data.mouse.mouse_r = false; + rc_can->data.keyboard.key_value = 0; + for (int i = 0; i < 6; i++) { + rc_can->data.keyboard.keys[i] = RC_CAN_KEY_NONE; + } + + // 注册CAN ID队列(从机模式需要接收数据) + if (rc_can->param.mode == RC_CAN_MODE_SLAVE) { + return RC_CAN_RegisterIds(rc_can); + } + + return DEVICE_OK; +} + +/** + * @brief 更新并发送数据到CAN总线 + * @param rc_can RC_CAN结构体指针 + * @param data_type 数据类型 + * @return DEVICE_OK 成功,其他值失败 + */ +int8_t RC_CAN_SendData(RC_CAN_t *rc_can, RC_CAN_DataType_t data_type) { + if (rc_can == NULL) { + return DEVICE_ERR_NULL; + } + if (rc_can->param.mode != RC_CAN_MODE_MASTER) { + return DEVICE_ERR; + } + BSP_CAN_StdDataFrame_t frame; + frame.dlc = 8; + // 边界裁剪宏 + #define RC_CAN_CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) + switch (data_type) { + case RC_CAN_DATA_JOYSTICK: { + frame.id = rc_can->param.joy_id; + float l_x = RC_CAN_CLAMP(rc_can->data.joy.ch_l_x, -0.999969f, 0.999969f); + float l_y = RC_CAN_CLAMP(rc_can->data.joy.ch_l_y, -0.999969f, 0.999969f); + float r_x = RC_CAN_CLAMP(rc_can->data.joy.ch_r_x, -0.999969f, 0.999969f); + float r_y = RC_CAN_CLAMP(rc_can->data.joy.ch_r_y, -0.999969f, 0.999969f); + int16_t l_x_i = (int16_t)(l_x * 32768.0f); + int16_t l_y_i = (int16_t)(l_y * 32768.0f); + int16_t r_x_i = (int16_t)(r_x * 32768.0f); + int16_t r_y_i = (int16_t)(r_y * 32768.0f); + frame.data[0] = (uint8_t)(l_x_i & 0xFF); + frame.data[1] = (uint8_t)((l_x_i >> 8) & 0xFF); + frame.data[2] = (uint8_t)(l_y_i & 0xFF); + frame.data[3] = (uint8_t)((l_y_i >> 8) & 0xFF); + frame.data[4] = (uint8_t)(r_x_i & 0xFF); + frame.data[5] = (uint8_t)((r_x_i >> 8) & 0xFF); + frame.data[6] = (uint8_t)(r_y_i & 0xFF); + frame.data[7] = (uint8_t)((r_y_i >> 8) & 0xFF); + break; + } + case RC_CAN_DATA_SWITCH: { + frame.id = rc_can->param.sw_id; + frame.data[0] = (uint8_t)(rc_can->data.sw.sw_l); + frame.data[1] = (uint8_t)(rc_can->data.sw.sw_r); + float ch_res = RC_CAN_CLAMP(rc_can->data.sw.ch_res, -0.999969f, 0.999969f); + int16_t ch_res_i = (int16_t)(ch_res * 32768.0f); + frame.data[2] = (uint8_t)(ch_res_i & 0xFF); + frame.data[3] = (uint8_t)((ch_res_i >> 8) & 0xFF); + frame.data[4] = 0; // 保留字节 + frame.data[5] = 0; // 保留字节 + frame.data[6] = 0; // 保留字节 + frame.data[7] = 0; // 保留字节 + break; + } + case RC_CAN_DATA_MOUSE: { + frame.id = rc_can->param.mouse_id; + // 鼠标x/y/z一般为增量,若有极限也可加clamp + int16_t x = (int16_t)(rc_can->data.mouse.x); + int16_t y = (int16_t)(rc_can->data.mouse.y); + int16_t z = (int16_t)(rc_can->data.mouse.z); + frame.data[0] = (uint8_t)(x & 0xFF); + frame.data[1] = (uint8_t)((x >> 8) & 0xFF); + frame.data[2] = (uint8_t)(y & 0xFF); + frame.data[3] = (uint8_t)((y >> 8) & 0xFF); + frame.data[4] = (uint8_t)(z & 0xFF); + frame.data[5] = (uint8_t)((z >> 8) & 0xFF); + frame.data[6] = (uint8_t)(rc_can->data.mouse.mouse_l ? 1 : 0); + frame.data[7] = (uint8_t)(rc_can->data.mouse.mouse_r ? 1 : 0); + break; + } + case RC_CAN_DATA_KEYBOARD: { + frame.id = rc_can->param.keyboard_id; + frame.data[0] = (uint8_t)(rc_can->data.keyboard.key_value & 0xFF); + frame.data[1] = (uint8_t)((rc_can->data.keyboard.key_value >> 8) & 0xFF); + for (int i = 0; i < 6; i++) { + frame.data[2 + i] = (i < 6) ? (uint8_t)(rc_can->data.keyboard.keys[i]) : 0; + } + break; + } + default: + return DEVICE_ERR; + } + #undef RC_CAN_CLAMP + if (BSP_CAN_Transmit(rc_can->param.can, BSP_CAN_FORMAT_STD_DATA, frame.id, + frame.data, frame.dlc) != BSP_OK) { + return DEVICE_ERR; + } + return DEVICE_OK; +} + +/** + * @brief 接收并更新CAN数据 + * @param rc_can RC_CAN结构体指针 + * @param data_type 数据类型 + * @return DEVICE_OK 成功,其他值失败 + */ +int8_t RC_CAN_Update(RC_CAN_t *rc_can, RC_CAN_DataType_t data_type) { + if (rc_can == NULL) { + return DEVICE_ERR_NULL; + } + + // 只有从机模式才能接收数据 + if (rc_can->param.mode != RC_CAN_MODE_SLAVE) { + return DEVICE_ERR; + } + BSP_CAN_Message_t msg; + + switch (data_type) { + case RC_CAN_DATA_JOYSTICK: + if (BSP_CAN_GetMessage(rc_can->param.can, rc_can->param.joy_id, &msg, + BSP_CAN_TIMEOUT_IMMEDIATE) != BSP_OK) { + return DEVICE_ERR; + } + // 解包数据 + int16_t ch_l_x = (int16_t)((msg.data[1] << 8) | msg.data[0]); + int16_t ch_l_y = (int16_t)((msg.data[3] << 8) | msg.data[2]); + int16_t ch_r_x = (int16_t)((msg.data[5] << 8) | msg.data[4]); + int16_t ch_r_y = (int16_t)((msg.data[7] << 8) | msg.data[6]); + + // 转换为浮点数(范围:-1.0到1.0) + rc_can->data.joy.ch_l_x = (float)ch_l_x / 32768.0f; + rc_can->data.joy.ch_l_y = (float)ch_l_y / 32768.0f; + rc_can->data.joy.ch_r_x = (float)ch_r_x / 32768.0f; + rc_can->data.joy.ch_r_y = (float)ch_r_y / 32768.0f; + break; + case RC_CAN_DATA_SWITCH: + if (BSP_CAN_GetMessage(rc_can->param.can, rc_can->param.sw_id, &msg, + BSP_CAN_TIMEOUT_IMMEDIATE) != BSP_OK) { + return DEVICE_ERR; + } + // 解包数据 + rc_can->data.sw.sw_l = (RC_CAN_SW_t)msg.data[0]; + rc_can->data.sw.sw_r = (RC_CAN_SW_t)msg.data[1]; + + int16_t ch_res = (int16_t)((msg.data[3] << 8) | msg.data[2]); + rc_can->data.sw.ch_res = (float)ch_res / 32768.0f; + break; + case RC_CAN_DATA_MOUSE: + if (BSP_CAN_GetMessage(rc_can->param.can, rc_can->param.mouse_id, &msg, + BSP_CAN_TIMEOUT_IMMEDIATE) != BSP_OK) { + return DEVICE_ERR; + } + // 解包数据 + int16_t x = (int16_t)((msg.data[1] << 8) | msg.data[0]); + int16_t y = (int16_t)((msg.data[3] << 8) | msg.data[2]); + int16_t z = (int16_t)((msg.data[5] << 8) | msg.data[4]); + rc_can->data.mouse.x = (float)x; + rc_can->data.mouse.y = (float)y; + rc_can->data.mouse.z = (float)z; + rc_can->data.mouse.mouse_l = (msg.data[6] & 0x01) ? true : false; + rc_can->data.mouse.mouse_r = (msg.data[7] & 0x01) ? true : false; + break; + case RC_CAN_DATA_KEYBOARD: + if (BSP_CAN_GetMessage(rc_can->param.can, rc_can->param.keyboard_id, &msg, + BSP_CAN_TIMEOUT_IMMEDIATE) != BSP_OK) { + return DEVICE_ERR; + } + if (msg.dlc < 2) { + return DEVICE_ERR; + } + // 解包数据 + rc_can->data.keyboard.key_value = + (uint16_t)((msg.data[1] << 8) | msg.data[0]); + for (int i = 0; i < 6 && (i + 2) < msg.dlc; i++) { + rc_can->data.keyboard.keys[i] = (RC_CAN_Key_t)(msg.data[2 + i]); + } + // 清空未使用的按键位置 + for (int i = (msg.dlc > 2 ? msg.dlc - 2 : 0); i < 6; i++) { + rc_can->data.keyboard.keys[i] = RC_CAN_KEY_NONE; + } + break; + default: + return DEVICE_ERR; + } + + // 更新header状态 + rc_can->header.online = true; + rc_can->header.last_online_time = BSP_TIME_Get_us(); + + return DEVICE_OK; +} + +/* Private functions -------------------------------------------------------- */ + +/** + * @brief 验证RC_CAN参数 + * @param param 参数指针 + * @return DEVICE_OK 成功,其他值失败 + */ +static int8_t RC_CAN_ValidateParams(const RC_CAN_Param_t *param) { + if (param == NULL) { + return DEVICE_ERR_NULL; + } + + // 检查CAN总线有效性 + if (param->can >= BSP_CAN_NUM) { + return DEVICE_ERR; + } + + // 检查工作模式有效性 + if (param->mode != RC_CAN_MODE_MASTER && param->mode != RC_CAN_MODE_SLAVE) { + return DEVICE_ERR; + } + + // 检查CAN ID是否重复 + if (param->joy_id == param->sw_id || param->joy_id == param->mouse_id || + param->joy_id == param->keyboard_id || param->sw_id == param->mouse_id || + param->sw_id == param->keyboard_id || + param->mouse_id == param->keyboard_id) { + return DEVICE_ERR; + } + + return DEVICE_OK; +} + +/** + * @brief 注册CAN ID + * @param rc_can RC_CAN结构体指针 + * @return DEVICE_OK 成功,其他值失败 + */ +static int8_t RC_CAN_RegisterIds(RC_CAN_t *rc_can) { + if (BSP_CAN_RegisterId(rc_can->param.can, rc_can->param.joy_id, 0) != + BSP_OK) { + return DEVICE_ERR; + } + if (BSP_CAN_RegisterId(rc_can->param.can, rc_can->param.sw_id, 0) != BSP_OK) { + return DEVICE_ERR; + } + if (BSP_CAN_RegisterId(rc_can->param.can, rc_can->param.mouse_id, 0) != + BSP_OK) { + return DEVICE_ERR; + } + if (BSP_CAN_RegisterId(rc_can->param.can, rc_can->param.keyboard_id, 0) != + BSP_OK) { + return DEVICE_ERR; + } + + return DEVICE_OK; +} + +int8_t RC_CAN_OFFLINE(RC_CAN_t *rc_can){ + if (rc_can == NULL) { + return DEVICE_ERR_NULL; + } + rc_can->header.online = false; + return DEVICE_OK; +} +/* USER CODE BEGIN */ + +/* USER CODE END */ diff --git a/device/rc_can.h b/device/rc_can.h new file mode 100644 index 0000000..e5c6d90 --- /dev/null +++ b/device/rc_can.h @@ -0,0 +1,157 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ----------------------------------------------------------------- */ +#include "bsp/can.h" +#include "device/device.h" +#include +#include + +/* USER INCLUDE BEGIN */ + +/* USER INCLUDE END */ + +/* USER DEFINE BEGIN */ + +/* USER DEFINE END */ + +/* Exported constants ------------------------------------------------------- */ + +/* Exported macro ----------------------------------------------------------- */ +/* Exported types ----------------------------------------------------------- */ +typedef enum { + RC_CAN_SW_ERR = 0, + RC_CAN_SW_UP = 1, + RC_CAN_SW_MID = 3, + RC_CAN_SW_DOWN = 2, +} RC_CAN_SW_t; + +typedef enum { + RC_CAN_MODE_MASTER = 0, // 主机模式 + RC_CAN_MODE_SLAVE = 1, // 从机模式 +} RC_CAN_Mode_t; + +typedef enum { + RC_CAN_DATA_JOYSTICK = 0, + RC_CAN_DATA_SWITCH, + RC_CAN_DATA_MOUSE, + RC_CAN_DATA_KEYBOARD +} RC_CAN_DataType_t; + +typedef enum { + RC_CAN_KEY_NONE = 0xFF, // 无按键 + RC_CAN_KEY_W = 0, + RC_CAN_KEY_S, + RC_CAN_KEY_A, + RC_CAN_KEY_D, + RC_CAN_KEY_SHIFT, + RC_CAN_KEY_CTRL, + RC_CAN_KEY_Q, + RC_CAN_KEY_E, + RC_CAN_KEY_R, + RC_CAN_KEY_F, + RC_CAN_KEY_G, + RC_CAN_KEY_Z, + RC_CAN_KEY_X, + RC_CAN_KEY_C, + RC_CAN_KEY_V, + RC_CAN_KEY_B, + RC_CAN_KEY_NUM, +} RC_CAN_Key_t; + +// 遥杆数据包 +typedef struct { + float ch_l_x; + float ch_l_y; + float ch_r_x; + float ch_r_y; +} RC_CAN_JoyData_t; + +// 拨杆数据包 +typedef struct { + RC_CAN_SW_t sw_l; // 左拨杆状态 + RC_CAN_SW_t sw_r; // 右拨杆状态 + float ch_res; // 第五通道 +} RC_CAN_SwitchData_t; + +// 鼠标数据包 +typedef struct { + float x; // 鼠标X轴移动 + float y; // 鼠标Y轴移动 + float z; // 鼠标Z轴(滚轮) + bool mouse_l; // 鼠标左键 + bool mouse_r; // 鼠标右键 +} RC_CAN_MouseData_t; + +// 键盘数据包 +typedef struct { + uint16_t key_value; // 键盘按键位映射 + RC_CAN_Key_t keys[16]; +} RC_CAN_KeyboardData_t; + + +typedef struct { + RC_CAN_JoyData_t joy; + RC_CAN_SwitchData_t sw; + RC_CAN_MouseData_t mouse; + RC_CAN_KeyboardData_t keyboard; +} RC_CAN_Data_t; + +// RC_CAN 参数结构 +typedef struct { + BSP_CAN_t can; // 使用的CAN总线 + RC_CAN_Mode_t mode; // 工作模式 + uint16_t joy_id; // 遥杆CAN ID + uint16_t sw_id; // 拨杆CAN ID + uint16_t mouse_id; // 鼠标CAN ID + uint16_t keyboard_id; // 键盘CAN ID +} RC_CAN_Param_t; + +// RC_CAN 主结构 +typedef struct { + DEVICE_Header_t header; + RC_CAN_Param_t param; + RC_CAN_Data_t data; +} RC_CAN_t; + +/* USER STRUCT BEGIN */ + +/* USER STRUCT END */ + +/* Exported functions prototypes -------------------------------------------- */ + +/** + * @brief 初始化RC CAN发送模块 + * @param rc_can RC_CAN结构体指针 + * @param param 初始化参数 + * @return DEVICE_OK 成功,其他值失败 + */ +int8_t RC_CAN_Init(RC_CAN_t *rc_can, RC_CAN_Param_t *param); + +/** + * @brief 更新并发送数据到CAN总线 + * @param rc_can RC_CAN结构体指针 + * @param data_type 数据类型 + * @return DEVICE_OK 成功,其他值失败 + */ +int8_t RC_CAN_SendData(RC_CAN_t *rc_can, RC_CAN_DataType_t data_type); + +/** + * @brief 接收并更新CAN数据 + * @param rc_can RC_CAN结构体指针 + * @param data_type 数据类型 + * @return DEVICE_OK 成功,其他值失败 + */ +int8_t RC_CAN_Update(RC_CAN_t *rc_can , RC_CAN_DataType_t data_type); + +int8_t RC_CAN_OFFLINE(RC_CAN_t *rc_can); +/* USER FUNCTION BEGIN */ + +/* USER FUNCTION END */ + +#ifdef __cplusplus +} +#endif diff --git a/module/2_axis_gimbal.c b/module/2_axis_gimbal.c new file mode 100644 index 0000000..53ff302 --- /dev/null +++ b/module/2_axis_gimbal.c @@ -0,0 +1,259 @@ +/* + * 云台模组 + */ + +/* Includes ----------------------------------------------------------------- */ +#include "bsp/can.h" +#include "bsp/time.h" +#include "component/filter.h" +#include "component/pid.h" +#include "device/motor_dm.h" +#include "device/motor_rm.h" +#include "gimbal.h" +#include + +/* Private typedef ---------------------------------------------------------- */ +/* Private define ----------------------------------------------------------- */ +/* Private macro ------------------------------------------------------------ */ +/* Private variables -------------------------------------------------------- */ +/* Private function -------------------------------------------------------- */ + +/** + * \brief 设置云台模式 + * + * \param c 包含云台数据的结构体 + * \param mode 要设置的模式 + * + * \return 函数运行结果 + */ +static int8_t Gimbal_SetMode(Gimbal_t *g, Gimbal_Mode_t mode) { + if (g == NULL) + return -1; + if (mode == g->mode) + return GIMBAL_OK; + + PID_Reset(&g->pid.yaw_angle); + PID_Reset(&g->pid.yaw_omega); + PID_Reset(&g->pid.pit_angle); + PID_Reset(&g->pid.pit_omega); + LowPassFilter2p_Reset(&g->filter_out.yaw, 0.0f); + LowPassFilter2p_Reset(&g->filter_out.pit, 0.0f); + + MOTOR_DM_Enable(&(g->param->yaw_motor)); + + AHRS_ResetEulr(&(g->setpoint.eulr)); /* 切换模式后重置设定值 */ + // if (g->mode == GIMBAL_MODE_RELAX) { + // if (mode == GIMBAL_MODE_ABSOLUTE) { + // g->setpoint.eulr.yaw = g->feedback.imu.eulr.yaw; + // } else if (mode == GIMBAL_MODE_RELATIVE) { + // g->setpoint.eulr.yaw = g->feedback.imu.eulr.yaw; + // } + // } + g->setpoint.eulr.pit = g->feedback.imu.eulr.rol; + g->setpoint.eulr.yaw = g->feedback.imu.eulr.yaw; + + g->mode = mode; + return 0; +} + +/* Exported functions ------------------------------------------------------- */ + +/** + * \brief 初始化云台 + * + * \param g 包含云台数据的结构体 + * \param param 包含云台参数的结构体指针 + * \param target_freq 任务预期的运行频率 + * + * \return 函数运行结果 + */ +int8_t Gimbal_Init(Gimbal_t *g, const Gimbal_Params_t *param, + float target_freq) { + if (g == NULL) + return -1; + + g->param = param; + g->mode = GIMBAL_MODE_RELAX; /* 设置默认模式 */ + + /* 初始化云台电机控制PID和LPF */ + PID_Init(&(g->pid.yaw_angle), KPID_MODE_NO_D, target_freq, + &(g->param->pid.yaw_angle)); + PID_Init(&(g->pid.yaw_omega), KPID_MODE_CALC_D, target_freq, + &(g->param->pid.yaw_omega)); + PID_Init(&(g->pid.pit_angle), KPID_MODE_NO_D, target_freq, + &(g->param->pid.pit_angle)); + PID_Init(&(g->pid.pit_omega), KPID_MODE_CALC_D, target_freq, + &(g->param->pid.pit_omega)); + + LowPassFilter2p_Init(&g->filter_out.yaw, target_freq, + g->param->low_pass_cutoff_freq.out); + LowPassFilter2p_Init(&g->filter_out.pit, target_freq, + g->param->low_pass_cutoff_freq.out); + g->limit.yaw.max = g->param->mech_zero.yaw + g->param->travel.yaw; + g->limit.yaw.min = g->param->mech_zero.yaw; + g->limit.pit.max = g->param->mech_zero.pit + g->param->travel.pit; + g->limit.pit.min = g->param->mech_zero.pit; + BSP_CAN_Init(); + + MOTOR_RM_Register(&(g->param->pit_motor)); + MOTOR_DM_Register(&(g->param->yaw_motor)); + + MOTOR_DM_Enable(&(g->param->yaw_motor)); + return 0; +} + +/** + * \brief 通过CAN设备更新云台反馈信息 + * + * \param gimbal 云台 + * \param can CAN设备 + * + * \return 函数运行结果 + */ +int8_t Gimbal_UpdateFeedback(Gimbal_t *gimbal) { + if (gimbal == NULL) + return -1; + + /* 更新RM电机反馈数据(pitch轴) */ + MOTOR_RM_Update(&(gimbal->param->pit_motor)); + MOTOR_RM_t *rm_motor = MOTOR_RM_GetMotor(&(gimbal->param->pit_motor)); + if (rm_motor != NULL) { + gimbal->feedback.motor.pit = rm_motor->feedback; + } + + /* 更新DM电机反馈数据(yaw轴) */ + MOTOR_DM_Update(&(gimbal->param->yaw_motor)); + MOTOR_DM_t *dm_motor = MOTOR_DM_GetMotor(&(gimbal->param->yaw_motor)); + if (dm_motor != NULL) { + gimbal->feedback.motor.yaw = dm_motor->motor.feedback; + } + + return 0; +} + +int8_t Gimbal_UpdateIMU(Gimbal_t *gimbal, const Gimbal_IMU_t *imu) { + + if (gimbal == NULL) { + return -1; + } + gimbal->feedback.imu.gyro = imu->gyro; + gimbal->feedback.imu.eulr = imu->eulr; +} + +/** + * \brief 运行云台控制逻辑 + * + * \param g 包含云台数据的结构体 + * \param g_cmd 云台控制指令 + * + * \return 函数运行结果 + */ +int8_t Gimbal_Control(Gimbal_t *g, Gimbal_CMD_t *g_cmd) { + if (g == NULL || g_cmd == NULL) { + return -1; + } + + g->dt = (BSP_TIME_Get_us() - g->lask_wakeup) / 1000000.0f; + g->lask_wakeup = BSP_TIME_Get_us(); + + Gimbal_SetMode(g, g_cmd->mode); + + /* 处理yaw控制命令,软件限位 - 使用电机绝对角度 */ + float delta_yaw = g_cmd->delta_yaw * g->dt * 1.5f; + if (g->param->travel.yaw > 0) { + /* 计算当前电机角度与IMU角度的偏差 */ + float motor_imu_offset = + g->feedback.motor.yaw.rotor_abs_angle - g->feedback.imu.eulr.yaw; + /* 处理跨越±π的情况 */ + if (motor_imu_offset > M_PI) + motor_imu_offset -= M_2PI; + if (motor_imu_offset < -M_PI) + motor_imu_offset += M_2PI; + + /* 计算到限位边界的距离 */ + const float delta_max = CircleError( + g->limit.yaw.max, (g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), + M_2PI); + const float delta_min = CircleError( + g->limit.yaw.min, (g->setpoint.eulr.yaw + motor_imu_offset + delta_yaw), + M_2PI); + + /* 限制控制命令 */ + if (delta_yaw > delta_max) + delta_yaw = delta_max; + if (delta_yaw < delta_min) + delta_yaw = delta_min; + } + CircleAdd(&(g->setpoint.eulr.yaw), delta_yaw, M_2PI); + + /* 处理pitch控制命令,软件限位 - 使用电机绝对角度 */ + float delta_pit = g_cmd->delta_pit * g->dt; + if (g->param->travel.pit > 0) { + /* 计算当前电机角度与IMU角度的偏差 */ + float motor_imu_offset = + g->feedback.motor.pit.rotor_abs_angle - g->feedback.imu.eulr.rol; + /* 处理跨越±π的情况 */ + if (motor_imu_offset > M_PI) + motor_imu_offset -= M_2PI; + if (motor_imu_offset < -M_PI) + motor_imu_offset += M_2PI; + + /* 计算到限位边界的距离 */ + const float delta_max = CircleError( + g->limit.pit.max, (g->setpoint.eulr.pit + motor_imu_offset + delta_pit), + M_2PI); + const float delta_min = CircleError( + g->limit.pit.min, (g->setpoint.eulr.pit + motor_imu_offset + delta_pit), + M_2PI); + + /* 限制控制命令 */ + if (delta_pit > delta_max) + delta_pit = delta_max; + if (delta_pit < delta_min) + delta_pit = delta_min; + } + CircleAdd(&(g->setpoint.eulr.pit), delta_pit, M_2PI); + + /* 控制相关逻辑 */ + float yaw_omega_set_point, pit_omega_set_point; + switch (g->mode) { + case GIMBAL_MODE_RELAX: + g->out.yaw = 0.0f; + g->out.pit = 0.0f; + break; + + case GIMBAL_MODE_ABSOLUTE: + yaw_omega_set_point = PID_Calc(&(g->pid.yaw_angle), g->setpoint.eulr.yaw, + g->feedback.imu.eulr.yaw, 0.0f, g->dt); + g->out.yaw = PID_Calc(&(g->pid.pit_omega), yaw_omega_set_point, + g->feedback.imu.gyro.z, 0.f, g->dt); + + pit_omega_set_point = PID_Calc(&(g->pid.pit_angle), g->setpoint.eulr.pit, + g->feedback.imu.eulr.rol, 0.0f, g->dt); + g->out.pit = PID_Calc(&(g->pid.pit_omega), pit_omega_set_point, + g->feedback.imu.gyro.y, 0.f, g->dt); + + break; + + /* 输出滤波 */ + g->out.yaw = LowPassFilter2p_Apply(&g->filter_out.yaw, g->out.yaw); + g->out.pit = LowPassFilter2p_Apply(&g->filter_out.pit, g->out.pit); + + return 0; + } +} + +/** + * \brief 云台输出 + * + * \param s 包含云台数据的结构体 + * \param out CAN设备云台输出结构体 + */ +void Gimbal_Output(Gimbal_t *g) { + MOTOR_RM_SetOutput(&g->param->pit_motor, g->out.pit); + MOTOR_MIT_Output_t output = {0}; + output.torque = g->out.yaw * 5.0f; // 乘以减速比 + output.kd = 0.3f; + MOTOR_RM_Ctrl(&g->param->pit_motor); + MOTOR_DM_MITCtrl(&g->param->yaw_motor, &output); +} diff --git a/module/2_axis_gimbal.h b/module/2_axis_gimbal.h new file mode 100644 index 0000000..e45faa7 --- /dev/null +++ b/module/2_axis_gimbal.h @@ -0,0 +1,179 @@ +/* + * 云台模组 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ----------------------------------------------------------------- */ +#include "component/ahrs.h" +#include "component/filter.h" +#include "component/pid.h" +#include "device/motor.h" +#include "device/motor_dm.h" +#include "device/motor_rm.h" + +/* Exported constants ------------------------------------------------------- */ +#define GIMBAL_OK (0) /* 运行正常 */ +#define GIMBAL_ERR (-1) /* 运行时发现了其他错误 */ +#define GIMBAL_ERR_NULL (-2) /* 运行时发现NULL指针 */ +#define GIMBAL_ERR_MODE (-3) /* 运行时配置了错误的CMD_GimbalMode_t */ + +/* Exported macro ----------------------------------------------------------- */ +/* Exported types ----------------------------------------------------------- */ + +typedef enum { + GIMBAL_MODE_RELAX, /* 放松模式,电机不输出。一般情况云台初始化之后的模式 */ + GIMBAL_MODE_ABSOLUTE, /* 绝对坐标系控制,控制在空间内的绝对姿态 */ + GIMBAL_MODE_RELATIVE, /* 相对坐标系控制,控制相对于底盘的姿态 */ +} Gimbal_Mode_t; + +typedef struct { + Gimbal_Mode_t mode; + float delta_yaw; + float delta_pit; +} Gimbal_CMD_t; + +/* 软件限位 */ +typedef struct { + float max; + float min; +} Gimbal_Limit_t; + +/* 云台参数的结构体,包含所有初始化用的参数,通常是const,存好几组。*/ +typedef struct { + MOTOR_RM_Param_t pit_motor; /* pitch轴电机参数 */ + MOTOR_DM_Param_t yaw_motor; /* yaw轴电机参数 */ + struct { + KPID_Params_t yaw_omega; /* yaw轴角速度环PID参数 */ + KPID_Params_t yaw_angle; /* yaw轴角位置环PID参数 */ + KPID_Params_t pit_omega; /* pitch轴角速度环PID参数 */ + KPID_Params_t pit_angle; /* pitch轴角位置环PID参数 */ + } pid; + + /* 低通滤波器截止频率 */ + struct { + float out; /* 电机输出 */ + float gyro; /* 陀螺仪数据 */ + } low_pass_cutoff_freq; + + struct { + float yaw; /* yaw轴机械限位 */ + float pit; /* pitch轴机械限位 */ + } mech_zero; + + struct { + float yaw; /* yaw轴机械限位行程 -1表示无限位 */ + float pit; /* pitch轴机械限位行程 -1表示无限位*/ + } travel; + +} Gimbal_Params_t; + +typedef struct { + AHRS_Gyro_t gyro; + AHRS_Eulr_t eulr; +} Gimbal_IMU_t; +/* 云台反馈数据的结构体,包含反馈控制用的反馈数据 */ +typedef struct { + Gimbal_IMU_t imu; + struct { + MOTOR_Feedback_t yaw; /* yaw轴电机反馈 */ + MOTOR_Feedback_t pit; /* pitch轴电机反馈 */ + } motor; +} Gimbal_Feedback_t; + +/* 云台输出数据的结构体*/ +typedef struct { + float yaw; /* yaw轴电机输出 */ + float pit; /* pitch轴电机输出 */ +} Gimbal_Output_t; +/* + * 运行的主结构体,所有这个文件里的函数都在操作这个结构体。 + * 包含了初始化参数,中间变量,输出变量。 + */ +typedef struct { + uint64_t lask_wakeup; + float dt; + + Gimbal_Params_t *param; /* 云台的参数,用Gimbal_Init设定 */ + /* 模块通用 */ + Gimbal_Mode_t mode; /* 云台模式 */ + + /* PID计算的目标值 */ + struct { + AHRS_Eulr_t eulr; /* 表示云台姿态的欧拉角 */ + } setpoint; + + struct { + KPID_t yaw_angle; /* yaw轴角位置环PID */ + KPID_t yaw_omega; /* yaw轴角速度环PID */ + KPID_t pit_angle; /* pitch轴角位置环PID */ + KPID_t pit_omega; /* pitch轴角速度环PID */ + } pid; + + struct { + Gimbal_Limit_t yaw; + Gimbal_Limit_t pit; + } limit; + + struct { + LowPassFilter2p_t yaw; + LowPassFilter2p_t pit; + } filter_out; + + Gimbal_Output_t out; /* 云台输出 */ + Gimbal_Feedback_t feedback; /* 反馈 */ + +} Gimbal_t; + +/* Exported functions prototypes -------------------------------------------- */ + +/** + * \brief 初始化云台 + * + * \param g 包含云台数据的结构体 + * \param param 包含云台参数的结构体指针 + * \param target_freq 任务预期的运行频率 + * + * \return 函数运行结果 + */ +int8_t Gimbal_Init(Gimbal_t *g, const Gimbal_Params_t *param, + float target_freq); + +/** + * \brief 通过CAN设备更新云台反馈信息 + * + * \param gimbal 云台 + * \param can CAN设备 + * + * \return 函数运行结果 + */ +int8_t Gimbal_UpdateFeedback(Gimbal_t *gimbal); + +int8_t Gimbal_UpdateIMU(Gimbal_t *gimbal, const Gimbal_IMU_t *imu); +/** + * \brief 运行云台控制逻辑 + * + * \param g 包含云台数据的结构体 + * \param fb 云台反馈信息 + * \param g_cmd 云台控制指令 + * \param dt_sec 两次调用的时间间隔 + * + * \return 函数运行结果 + */ +int8_t Gimbal_Control(Gimbal_t *g, Gimbal_CMD_t *g_cmd); + +/** + * \brief 云台输出 + * + * \param s 包含云台数据的结构体 + * \param out CAN设备云台输出结构体 + */ +void Gimbal_Output(Gimbal_t *g); + +#ifdef __cplusplus +} +#endif diff --git a/module/config.c b/module/config.c index e69de29..cb4ed7c 100644 --- a/module/config.c +++ b/module/config.c @@ -0,0 +1,31 @@ +/* + * 配置相关 + */ + +/* Includes ----------------------------------------------------------------- */ +#include "module/config.h" + + +/* Private typedef ---------------------------------------------------------- */ +/* Private define ----------------------------------------------------------- */ +/* Private macro ------------------------------------------------------------ */ +/* Private variables -------------------------------------------------------- */ + +/* Exported variables ------------------------------------------------------- */ + +// 机器人参数配置 +Config_RobotParam_t robot_config = { + + +}; + +/* Private function prototypes ---------------------------------------------- */ +/* Exported functions ------------------------------------------------------- */ + +/** + * @brief 获取机器人配置参数 + * @return 机器人配置参数指针 + */ +Config_RobotParam_t* Config_GetRobotParam(void) { + return &robot_config; +} \ No newline at end of file diff --git a/module/config.h b/module/config.h index e69de29..446fb6c 100644 --- a/module/config.h +++ b/module/config.h @@ -0,0 +1,27 @@ +/* + * 配置相关 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct { + +} Config_RobotParam_t; + +/* Exported functions prototypes -------------------------------------------- */ + +/** + * @brief 获取机器人配置参数 + * @return 机器人配置参数指针 + */ +Config_RobotParam_t* Config_GetRobotParam(void); + +#ifdef __cplusplus +} +#endif