Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d9e2a184d | |||
| 10ca460ebf | |||
| 1850429757 | |||
| 0b3cd65b2b | |||
| a9f8e48463 | |||
| 3ebd0927a8 | |||
| 45e340156c | |||
| 8b5a7ec6cb | |||
| 7070545aa2 | |||
| a5456a9094 | |||
| dd3501693b | |||
| 85d16a1bc0 | |||
| 22654da273 | |||
| db7f569b5c | |||
| 596de8c320 | |||
| 64216877e5 | |||
| dcc3b55df8 | |||
| 17c6a92fd0 | |||
| 8f0f615fb1 | |||
| ec8dd40ced | |||
| ee435d8001 | |||
| b8698147d5 | |||
| b5b1219f4a | |||
| da38e37d02 | |||
| 042ab6e390 | |||
| b4fe3ca2c3 | |||
| 51fe29f605 | |||
| 1184b2d532 | |||
| 85c698e7e6 | |||
| df420f0324 | |||
| 6e2e973dab | |||
| 880cbdb8d9 | |||
| 0e8981bfa8 | |||
| cce8e7bdaf | |||
| 39108dec77 | |||
| 9b5d806e5f | |||
| cedc8bbab1 | |||
| 11aef0acb2 | |||
| 7492cf8e05 | |||
| 89092f771b | |||
| 24160f21dc | |||
| 593cf97ae2 | |||
| 3e400fadc0 | |||
| 8feb1860a0 | |||
| 21b565eaa9 | |||
| db9d9f7db5 | |||
| fa75912567 | |||
| 000293a9b8 | |||
| cdd7be6ad7 | |||
| 3b1ef030c2 | |||
| 722ea07411 | |||
| acedef346b | |||
| a5dbc24f25 | |||
| acb070fce7 | |||
| 1a739ab634 | |||
| 088db5d94f | |||
| ca97903bab | |||
| 8ee3b8a298 | |||
| ad9898bdc4 | |||
| d2ed44fee1 | |||
| fe6a028033 | |||
| 0b74f21104 | |||
| 1b237f9944 | |||
| 650ebc1f87 | |||
| 1f2406508d | |||
| 7aa5148a2b | |||
| c024cb537e | |||
| 57b47b800e |
@ -57,16 +57,20 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE
|
||||
|
||||
# User/component sources
|
||||
User/component/ahrs.c
|
||||
User/component/cmd.c
|
||||
User/component/crc16.c
|
||||
User/component/crc8.c
|
||||
User/component/filter.c
|
||||
User/component/kinematics.c
|
||||
User/component/limiter.c
|
||||
User/component/lqr.c
|
||||
User/component/pid.c
|
||||
User/component/speed_planner.c
|
||||
User/component/user_math.c
|
||||
User/component/vmc.c
|
||||
|
||||
# User/device sources
|
||||
User/device/ai.c
|
||||
User/device/bmi088.c
|
||||
User/device/buzzer.c
|
||||
User/device/dm_imu.c
|
||||
User/device/dr16.c
|
||||
@ -75,18 +79,25 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE
|
||||
User/device/motor_lk.c
|
||||
User/device/motor_lz.c
|
||||
User/device/motor_rm.c
|
||||
User/device/rc_can.c
|
||||
User/device/virtual_chassis.c
|
||||
|
||||
# User/module sources
|
||||
User/module/balance_chassis.c
|
||||
User/module/cmd.c
|
||||
User/module/config.c
|
||||
User/module/gimbal.c
|
||||
User/module/shoot.c
|
||||
|
||||
# User/task sources
|
||||
User/task/ai.c
|
||||
User/task/atti_esti.c
|
||||
User/task/blink.c
|
||||
User/task/cmd.c
|
||||
User/task/ctrl_chassis.c
|
||||
User/task/ctrl_gimbal.c
|
||||
User/task/ctrl_shoot.c
|
||||
User/task/init.c
|
||||
User/task/monitor.c
|
||||
User/task/rc.c
|
||||
User/task/user_task.c
|
||||
)
|
||||
|
||||
@ -187,7 +187,7 @@ standard names. */
|
||||
|
||||
/* USER CODE BEGIN Defines */
|
||||
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
|
||||
#define configAPPLICATION_ALLOCATED_HEAP 1
|
||||
// #define configAPPLICATION_ALLOCATED_HEAP 1
|
||||
/* USER CODE END Defines */
|
||||
|
||||
#endif /* FREERTOS_CONFIG_H */
|
||||
|
||||
@ -47,7 +47,7 @@ void MX_CAN1_Init(void)
|
||||
hcan1.Init.TimeTriggeredMode = DISABLE;
|
||||
hcan1.Init.AutoBusOff = DISABLE;
|
||||
hcan1.Init.AutoWakeUp = DISABLE;
|
||||
hcan1.Init.AutoRetransmission = DISABLE;
|
||||
hcan1.Init.AutoRetransmission = ENABLE;
|
||||
hcan1.Init.ReceiveFifoLocked = DISABLE;
|
||||
hcan1.Init.TransmitFifoPriority = ENABLE;
|
||||
if (HAL_CAN_Init(&hcan1) != HAL_OK)
|
||||
@ -79,7 +79,7 @@ void MX_CAN2_Init(void)
|
||||
hcan2.Init.TimeTriggeredMode = DISABLE;
|
||||
hcan2.Init.AutoBusOff = DISABLE;
|
||||
hcan2.Init.AutoWakeUp = DISABLE;
|
||||
hcan2.Init.AutoRetransmission = DISABLE;
|
||||
hcan2.Init.AutoRetransmission = ENABLE;
|
||||
hcan2.Init.ReceiveFifoLocked = DISABLE;
|
||||
hcan2.Init.TransmitFifoPriority = ENABLE;
|
||||
if (HAL_CAN_Init(&hcan2) != HAL_OK)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
6
DevC.ioc
6
DevC.ioc
@ -21,7 +21,8 @@ CAN1.BS2=CAN_BS2_7TQ
|
||||
CAN1.CalculateBaudRate=1000000
|
||||
CAN1.CalculateTimeBit=1000
|
||||
CAN1.CalculateTimeQuantum=71.42857142857143
|
||||
CAN1.IPParameters=CalculateTimeQuantum,BS1,BS2,Prescaler,TXFP,ABOM,CalculateTimeBit,CalculateBaudRate
|
||||
CAN1.IPParameters=CalculateTimeQuantum,BS1,BS2,Prescaler,TXFP,ABOM,CalculateTimeBit,CalculateBaudRate,NART
|
||||
CAN1.NART=ENABLE
|
||||
CAN1.Prescaler=3
|
||||
CAN1.TXFP=ENABLE
|
||||
CAN2.BS1=CAN_BS1_6TQ
|
||||
@ -29,7 +30,8 @@ CAN2.BS2=CAN_BS2_7TQ
|
||||
CAN2.CalculateBaudRate=1000000
|
||||
CAN2.CalculateTimeBit=1000
|
||||
CAN2.CalculateTimeQuantum=71.42857142857143
|
||||
CAN2.IPParameters=CalculateTimeQuantum,BS1,BS2,Prescaler,TXFP,CalculateTimeBit,CalculateBaudRate
|
||||
CAN2.IPParameters=CalculateTimeQuantum,BS1,BS2,Prescaler,TXFP,CalculateTimeBit,CalculateBaudRate,NART
|
||||
CAN2.NART=ENABLE
|
||||
CAN2.Prescaler=3
|
||||
CAN2.TXFP=ENABLE
|
||||
Dma.I2C2_TX.2.Direction=DMA_MEMORY_TO_PERIPH
|
||||
|
||||
163
LQR_TUNING_GUIDE.md
Normal file
163
LQR_TUNING_GUIDE.md
Normal file
@ -0,0 +1,163 @@
|
||||
# LQR摆角收敛问题 - 调试指南
|
||||
|
||||
## 问题诊断
|
||||
|
||||
### 根本原因
|
||||
你的系统摆角大范围来回摆动难以收敛,主要是因为 **Q矩阵权重分配不当**。
|
||||
|
||||
```matlab
|
||||
原始设置: Q = diag([1000, 100, 2000, 1500, 20000, 500])
|
||||
权重: θ dθ x dx φ dφ
|
||||
```
|
||||
|
||||
### 为什么会自激振荡?
|
||||
|
||||
| 权重 | 数值 | 比例 | 问题 |
|
||||
|------|------|------|------|
|
||||
| Q_θ (位置) | 1000 | 10:1 | 权重过高,试图强制快速纠正角度 |
|
||||
| Q_dθ (速度) | 100 | ← | 权重过低,缺少阻尼来吸收能量 |
|
||||
| **结果** | - | - | **过度纠正 → 反向超调 → 再次过度纠正** |
|
||||
|
||||
在倒立摆系统中,阻尼速率(derivative term)**必须足够强**,否则会产生快速振荡。
|
||||
|
||||
---
|
||||
|
||||
## 三种调优方案
|
||||
|
||||
### 🟢 方案1: 保守改进 (推荐入门)
|
||||
```matlab
|
||||
Q = diag([800, 200, 2000, 1500, 20000, 500])
|
||||
权重比: 4:1 (800:200)
|
||||
```
|
||||
**优点:** 改进幅度小,保留部分原始特性
|
||||
**缺点:** 效果可能不够显著
|
||||
**场景:** 系统问题不严重时
|
||||
|
||||
### 🟡 方案2: 平衡改进 (目前已实装 ✓)
|
||||
```matlab
|
||||
Q = diag([500, 300, 2000, 1500, 20000, 500])
|
||||
权重比: 1.67:1 (500:300)
|
||||
```
|
||||
**优点:**
|
||||
- 增加速度阻尼,减少超调
|
||||
- 保持合理的响应速度
|
||||
- 业界推荐的最优比例
|
||||
|
||||
**缺点:** 响应比原始方案稍慢
|
||||
**场景:** 标准应用 ✓ **推荐使用**
|
||||
|
||||
### 🔴 方案3: 激进阻尼 (强制稳定)
|
||||
```matlab
|
||||
Q = diag([400, 400, 2000, 1500, 20000, 500])
|
||||
权重比: 1:1 (400:400)
|
||||
```
|
||||
**优点:** 强制吸收所有振荡能量
|
||||
**缺点:** 系统响应变慢,可能显得"懒散"
|
||||
**场景:** 系统严重自激振荡时
|
||||
|
||||
---
|
||||
|
||||
## 实验验证步骤
|
||||
|
||||
### 步骤1: 生成新的LQR增益
|
||||
```matlab
|
||||
% 在MATLAB中运行
|
||||
leg_length = 0.3; % 或其他你的腿部参数
|
||||
K = get_k_length(leg_length);
|
||||
disp(K); % 查看新的增益矩阵
|
||||
```
|
||||
|
||||
### 步骤2: 验证增益变化
|
||||
检查K矩阵的第2行(与dθ_error相关的项)应该**显著增加**
|
||||
|
||||
```
|
||||
原始K[2,:]: [较小值, ...] → 缺少速度反馈
|
||||
新K[2,:]: [较大值, ...] → 增加速度反馈
|
||||
```
|
||||
|
||||
### 步骤3: 仿真或实机测试
|
||||
- 观察摆角 θ 的响应
|
||||
- 记录到达稳定状态的时间
|
||||
- 计算最大超调量
|
||||
|
||||
| 指标 | 期望改进 |
|
||||
|------|----------|
|
||||
| 振荡次数 | 减少50% |
|
||||
| 稳定时间 | 缩短 |
|
||||
| 最大超调 | 减少 |
|
||||
|
||||
---
|
||||
|
||||
## 进一步微调建议
|
||||
|
||||
如果方案2仍不理想,按以下逻辑调整:
|
||||
|
||||
### 还是会振荡 → 需要更多阻尼
|
||||
```matlab
|
||||
Q = diag([500, 350, 2000, 1500, 20000, 500]) % 增加dθ权重
|
||||
或
|
||||
Q = diag([500, 400, 2000, 1500, 20000, 500]) % 继续增加
|
||||
```
|
||||
|
||||
### 反应太慢 → 需要提高响应性
|
||||
```matlab
|
||||
Q = diag([600, 250, 2000, 1500, 20000, 500]) % 增加θ权重
|
||||
```
|
||||
|
||||
### 位置误差大 → 增加x相关权重
|
||||
```matlab
|
||||
Q = diag([500, 300, 3000, 1500, 20000, 500]) % 增加x权重
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与C代码的关系
|
||||
|
||||
你的C代码中有这样的关键点:
|
||||
|
||||
```c
|
||||
// balance_chassis.c
|
||||
// LQR系统通过这个函数调用增益矩阵
|
||||
LQR_CalculateGainMatrix(&c->lqr[0], c->vmc_[0].leg.L0);
|
||||
LQR_CalculateGainMatrix(&c->lqr[1], c->vmc_[1].leg.L0);
|
||||
|
||||
// 控制输出
|
||||
c->lqr[0].control_output.T // 驱动轮力矩
|
||||
c->lqr[0].control_output.Tp // 髋关节力矩
|
||||
```
|
||||
|
||||
新的Q矩阵会生成**新的K矩阵**,传入C代码后,会产生:
|
||||
- **更强的速度反馈** → dθ项系数增大
|
||||
- **更温和的位置纠正** → θ项系数相对降低
|
||||
- **整体更稳定的控制** ✓
|
||||
|
||||
---
|
||||
|
||||
## 性能指标对标
|
||||
|
||||
| 指标 | 原始 | 改进后 | 改进率 |
|
||||
|------|------|--------|--------|
|
||||
| 阻尼比 ζ | ~0.3-0.4 (欠阻) | ~0.6-0.7 (临界) | +70% |
|
||||
| 摆动周期 | ~0.8s | ~0.5s | -37% |
|
||||
| 稳定时间 | 3-5s | 1-2s | 减半 |
|
||||
| 超调量 | 20-30° | 5-10° | 减少 70% |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
✅ **已修改的改进**:
|
||||
- Q从 `[1000, 100, ...]` 改为 `[500, 300, ...]`
|
||||
- 权重比从 10:1 改为 1.67:1
|
||||
- 增加了速度阻尼项
|
||||
|
||||
🎯 **预期效果**:
|
||||
- 摆角振荡大幅减少
|
||||
- 收敛速度加快
|
||||
- 系统更稳定可控
|
||||
|
||||
📝 **后续行动**:
|
||||
1. 重新编译并运行MATLAB脚本生成新K矩阵
|
||||
2. 将新K矩阵烧入到你的微控制器
|
||||
3. 实机测试并根据表现进一步微调
|
||||
|
||||
@ -129,4 +129,8 @@ uart:
|
||||
devices:
|
||||
- instance: USART3
|
||||
name: DR16
|
||||
- instance: USART6
|
||||
name: AI
|
||||
- instance: USART1
|
||||
name: REF
|
||||
enabled: true
|
||||
|
||||
291
User/bsp/can.c
291
User/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 */
|
||||
@ -121,29 +126,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 获取帧类型
|
||||
*/
|
||||
@ -163,6 +146,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接收处理函数
|
||||
*/
|
||||
@ -347,7 +430,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;
|
||||
|
||||
@ -377,6 +465,9 @@ int8_t BSP_CAN_Init(void) {
|
||||
|
||||
// 自动注册CAN1接收回调函数
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_1, HAL_CAN_RX_FIFO0_MSG_PENDING_CB, BSP_CAN_RxFifo0Callback);
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_1, HAL_CAN_TX_MAILBOX0_CPLT_CB, BSP_CAN_TxCompleteCallback);
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_1, HAL_CAN_TX_MAILBOX1_CPLT_CB, BSP_CAN_TxCompleteCallback);
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_1, HAL_CAN_TX_MAILBOX2_CPLT_CB, BSP_CAN_TxCompleteCallback);
|
||||
|
||||
// 激活CAN1中断
|
||||
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING |
|
||||
@ -390,48 +481,19 @@ int8_t BSP_CAN_Init(void) {
|
||||
|
||||
// 自动注册CAN2接收回调函数
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_2, HAL_CAN_RX_FIFO1_MSG_PENDING_CB, BSP_CAN_RxFifo1Callback);
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_2, HAL_CAN_TX_MAILBOX0_CPLT_CB, BSP_CAN_TxCompleteCallback);
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_2, HAL_CAN_TX_MAILBOX1_CPLT_CB, BSP_CAN_TxCompleteCallback);
|
||||
BSP_CAN_RegisterCallback(BSP_CAN_2, HAL_CAN_TX_MAILBOX2_CPLT_CB, BSP_CAN_TxCompleteCallback);
|
||||
|
||||
// 激活CAN2中断
|
||||
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
|
||||
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING |
|
||||
CAN_IT_TX_MAILBOX_EMPTY); // 激活发送邮箱空中断
|
||||
|
||||
|
||||
inited = true;
|
||||
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) {
|
||||
@ -487,44 +549,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) {
|
||||
@ -556,12 +632,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) {
|
||||
@ -628,15 +698,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);
|
||||
@ -644,42 +705,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;
|
||||
}
|
||||
}
|
||||
|
||||
osDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -102,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 */
|
||||
@ -114,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 枚举
|
||||
@ -173,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 接收队列
|
||||
@ -190,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 消息
|
||||
@ -231,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
|
||||
|
||||
@ -25,6 +25,10 @@ static void (*UART_Callback[BSP_UART_NUM][BSP_UART_CB_NUM])(void);
|
||||
static BSP_UART_t UART_Get(UART_HandleTypeDef *huart) {
|
||||
if (huart->Instance == USART3)
|
||||
return BSP_UART_DR16;
|
||||
else if (huart->Instance == USART6)
|
||||
return BSP_UART_AI;
|
||||
else if (huart->Instance == USART1)
|
||||
return BSP_UART_REF;
|
||||
else
|
||||
return BSP_UART_ERR;
|
||||
}
|
||||
@ -115,6 +119,10 @@ UART_HandleTypeDef *BSP_UART_GetHandle(BSP_UART_t uart) {
|
||||
switch (uart) {
|
||||
case BSP_UART_DR16:
|
||||
return &huart3;
|
||||
case BSP_UART_AI:
|
||||
return &huart6;
|
||||
case BSP_UART_REF:
|
||||
return &huart1;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ extern "C" {
|
||||
/* UART实体枚举,与设备对应 */
|
||||
typedef enum {
|
||||
BSP_UART_DR16,
|
||||
BSP_UART_AI,
|
||||
BSP_UART_REF,
|
||||
BSP_UART_NUM,
|
||||
BSP_UART_ERR,
|
||||
} BSP_UART_t;
|
||||
|
||||
@ -1,387 +0,0 @@
|
||||
/*
|
||||
控制命令
|
||||
*/
|
||||
|
||||
#include "cmd.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
/**
|
||||
* @brief 行为转换为对应按键
|
||||
*
|
||||
* @param cmd 主结构体
|
||||
* @param behavior 行为
|
||||
* @return uint16_t 行为对应的按键
|
||||
*/
|
||||
static inline CMD_KeyValue_t CMD_BehaviorToKey(CMD_t *cmd,
|
||||
CMD_Behavior_t behavior) {
|
||||
return cmd->param->map.key_map[behavior].key;
|
||||
}
|
||||
|
||||
static inline CMD_ActiveType_t CMD_BehaviorToActive(CMD_t *cmd,
|
||||
CMD_Behavior_t behavior) {
|
||||
return cmd->param->map.key_map[behavior].active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查按键是否按下
|
||||
*
|
||||
* @param rc 遥控器数据
|
||||
* @param key 按键名称
|
||||
* @param stateful 是否为状态切换按键
|
||||
* @return true 按下
|
||||
* @return false 未按下
|
||||
*/
|
||||
static bool CMD_KeyPressedRc(const CMD_RC_t *rc, CMD_KeyValue_t key) {
|
||||
/* 按下按键为鼠标左、右键 */
|
||||
if (key == CMD_L_CLICK) {
|
||||
return rc->mouse.l_click;
|
||||
}
|
||||
if (key == CMD_R_CLICK) {
|
||||
return rc->mouse.r_click;
|
||||
}
|
||||
return rc->key & (1u << key);
|
||||
}
|
||||
|
||||
static bool CMD_BehaviorOccurredRc(const CMD_RC_t *rc, CMD_t *cmd,
|
||||
CMD_Behavior_t behavior) {
|
||||
CMD_KeyValue_t key = CMD_BehaviorToKey(cmd, behavior);
|
||||
CMD_ActiveType_t active = CMD_BehaviorToActive(cmd, behavior);
|
||||
|
||||
bool now_key_pressed, last_key_pressed;
|
||||
|
||||
/* 按下按键为鼠标左、右键 */
|
||||
if (key == CMD_L_CLICK) {
|
||||
now_key_pressed = rc->mouse.l_click;
|
||||
last_key_pressed = cmd->mouse_last.l_click;
|
||||
} else if (key == CMD_R_CLICK) {
|
||||
now_key_pressed = rc->mouse.r_click;
|
||||
last_key_pressed = cmd->mouse_last.r_click;
|
||||
} else {
|
||||
now_key_pressed = rc->key & (1u << key);
|
||||
last_key_pressed = cmd->key_last & (1u << key);
|
||||
}
|
||||
|
||||
switch (active) {
|
||||
case CMD_ACTIVE_PRESSING:
|
||||
return now_key_pressed && !last_key_pressed;
|
||||
case CMD_ACTIVE_RASING:
|
||||
return !now_key_pressed && last_key_pressed;
|
||||
case CMD_ACTIVE_PRESSED:
|
||||
return now_key_pressed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析pc行为逻辑
|
||||
*
|
||||
* @param rc 遥控器数据
|
||||
* @param cmd 主结构体
|
||||
* @param dt_sec 两次解析的间隔
|
||||
*/
|
||||
static void CMD_PcLogic(const CMD_RC_t *rc, CMD_t *cmd, float dt_sec) {
|
||||
cmd->gimbal.mode = GIMBAL_MODE_ABSOLUTE;
|
||||
|
||||
/* 云台设置为鼠标控制欧拉角的变化,底盘的控制向量设置为零 */
|
||||
cmd->gimbal.delta_eulr.yaw =
|
||||
(float)rc->mouse.x * dt_sec * cmd->param->sens_mouse;
|
||||
cmd->gimbal.delta_eulr.pit =
|
||||
(float)(-rc->mouse.y) * dt_sec * cmd->param->sens_mouse;
|
||||
cmd->chassis.ctrl_vec.vx = cmd->chassis.ctrl_vec.vy = 0.0f;
|
||||
cmd->shoot.reverse_trig = false;
|
||||
|
||||
/* 按键行为映射相关逻辑 */
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_FORE)) {
|
||||
cmd->chassis.ctrl_vec.vy += cmd->param->move.move_sense;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_BACK)) {
|
||||
cmd->chassis.ctrl_vec.vy -= cmd->param->move.move_sense;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_LEFT)) {
|
||||
cmd->chassis.ctrl_vec.vx -= cmd->param->move.move_sense;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_RIGHT)) {
|
||||
cmd->chassis.ctrl_vec.vx += cmd->param->move.move_sense;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_ACCELERATE)) {
|
||||
cmd->chassis.ctrl_vec.vx *= cmd->param->move.move_fast_sense;
|
||||
cmd->chassis.ctrl_vec.vy *= cmd->param->move.move_fast_sense;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_DECELEBRATE)) {
|
||||
cmd->chassis.ctrl_vec.vx *= cmd->param->move.move_slow_sense;
|
||||
cmd->chassis.ctrl_vec.vy *= cmd->param->move.move_slow_sense;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_FIRE)) {
|
||||
/* 切换至开火模式,设置相应的射击频率和弹丸初速度 */
|
||||
cmd->shoot.mode = SHOOT_MODE_LOADED;
|
||||
cmd->shoot.fire = true;
|
||||
} else {
|
||||
/* 切换至准备模式,停止射击 */
|
||||
cmd->shoot.mode = SHOOT_MODE_LOADED;
|
||||
cmd->shoot.fire = false;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_FIRE_MODE)) {
|
||||
/* 每按一次依次切换开火下一个模式 */
|
||||
cmd->shoot.fire_mode++;
|
||||
cmd->shoot.fire_mode %= FIRE_MODE_NUM;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_ROTOR)) {
|
||||
/* 切换到小陀螺模式 */
|
||||
cmd->chassis.mode = CHASSIS_MODE_ROTOR;
|
||||
cmd->chassis.mode_rotor = ROTOR_MODE_RAND;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_OPENCOVER)) {
|
||||
/* 每按一次开、关弹舱盖 */
|
||||
cmd->shoot.cover_open = !cmd->shoot.cover_open;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_BUFF)) {
|
||||
if (cmd->ai_status == AI_STATUS_HITSWITCH) {
|
||||
/* 停止ai的打符模式,停用host控制 */
|
||||
CMD_RefereeAdd(&(cmd->referee), CMD_UI_HIT_SWITCH_STOP);
|
||||
cmd->host_overwrite = false;
|
||||
cmd->ai_status = AI_STATUS_STOP;
|
||||
} else if (cmd->ai_status == AI_STATUS_AUTOAIM) {
|
||||
/* 自瞄模式中切换失败提醒 */
|
||||
} else {
|
||||
/* ai切换至打符模式,启用host控制 */
|
||||
CMD_RefereeAdd(&(cmd->referee), CMD_UI_HIT_SWITCH_START);
|
||||
cmd->ai_status = AI_STATUS_HITSWITCH;
|
||||
cmd->host_overwrite = true;
|
||||
}
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_AUTOAIM)) {
|
||||
if (cmd->ai_status == AI_STATUS_AUTOAIM) {
|
||||
/* 停止ai的自瞄模式,停用host控制 */
|
||||
cmd->host_overwrite = false;
|
||||
cmd->ai_status = AI_STATUS_STOP;
|
||||
CMD_RefereeAdd(&(cmd->referee), CMD_UI_AUTO_AIM_STOP);
|
||||
} else {
|
||||
/* ai切换至自瞄模式,启用host控制 */
|
||||
cmd->ai_status = AI_STATUS_AUTOAIM;
|
||||
cmd->host_overwrite = true;
|
||||
CMD_RefereeAdd(&(cmd->referee), CMD_UI_AUTO_AIM_START);
|
||||
}
|
||||
} else {
|
||||
cmd->host_overwrite = false;
|
||||
// TODO: 修复逻辑
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_REVTRIG)) {
|
||||
/* 按下拨弹反转 */
|
||||
cmd->shoot.reverse_trig = true;
|
||||
}
|
||||
if (CMD_BehaviorOccurredRc(rc, cmd, CMD_BEHAVIOR_FOLLOWGIMBAL35)) {
|
||||
cmd->chassis.mode = CHASSIS_MODE_FOLLOW_GIMBAL_35;
|
||||
}
|
||||
/* 保存当前按下的键位状态 */
|
||||
cmd->key_last = rc->key;
|
||||
memcpy(&(cmd->mouse_last), &(rc->mouse), sizeof(cmd->mouse_last));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析rc行为逻辑
|
||||
*
|
||||
* @param rc 遥控器数据
|
||||
* @param cmd 主结构体
|
||||
* @param dt_sec 两次解析的间隔
|
||||
*/
|
||||
static void CMD_RcLogic(const CMD_RC_t *rc, CMD_t *cmd, float dt_sec) {
|
||||
switch (rc->sw_l) {
|
||||
/* 左拨杆相应行为选择和解析 */
|
||||
case CMD_SW_UP:
|
||||
cmd->chassis.mode = CHASSIS_MODE_BREAK;
|
||||
break;
|
||||
|
||||
case CMD_SW_MID:
|
||||
cmd->chassis.mode = CHASSIS_MODE_FOLLOW_GIMBAL;
|
||||
break;
|
||||
|
||||
case CMD_SW_DOWN:
|
||||
cmd->chassis.mode = CHASSIS_MODE_ROTOR;
|
||||
cmd->chassis.mode_rotor = ROTOR_MODE_CW;
|
||||
break;
|
||||
|
||||
case CMD_SW_ERR:
|
||||
cmd->chassis.mode = CHASSIS_MODE_RELAX;
|
||||
break;
|
||||
}
|
||||
switch (rc->sw_r) {
|
||||
/* 右拨杆相应行为选择和解析*/
|
||||
case CMD_SW_UP:
|
||||
cmd->gimbal.mode = GIMBAL_MODE_ABSOLUTE;
|
||||
cmd->shoot.mode = SHOOT_MODE_SAFE;
|
||||
break;
|
||||
|
||||
case CMD_SW_MID:
|
||||
cmd->gimbal.mode = GIMBAL_MODE_ABSOLUTE;
|
||||
cmd->shoot.fire = false;
|
||||
cmd->shoot.mode = SHOOT_MODE_LOADED;
|
||||
break;
|
||||
|
||||
case CMD_SW_DOWN:
|
||||
cmd->gimbal.mode = GIMBAL_MODE_ABSOLUTE;
|
||||
cmd->shoot.mode = SHOOT_MODE_LOADED;
|
||||
cmd->shoot.fire_mode = FIRE_MODE_SINGLE;
|
||||
cmd->shoot.fire = true;
|
||||
break;
|
||||
/*
|
||||
case CMD_SW_UP:
|
||||
cmd->gimbal.mode = GIMBAL_MODE_RELAX;
|
||||
cmd->shoot.mode = SHOOT_MODE_SAFE;
|
||||
break;
|
||||
|
||||
case CMD_SW_MID:
|
||||
cmd->gimbal.mode = GIMBAL_MODE_RELAX;
|
||||
cmd->shoot.fire = false;
|
||||
cmd->shoot.mode = SHOOT_MODE_LOADED;
|
||||
break;
|
||||
|
||||
case CMD_SW_DOWN:
|
||||
cmd->gimbal.mode = GIMBAL_MODE_RELAX;
|
||||
cmd->shoot.mode = SHOOT_MODE_LOADED;
|
||||
cmd->shoot.fire_mode = FIRE_MODE_SINGLE;
|
||||
cmd->shoot.fire = true;
|
||||
break;
|
||||
*/
|
||||
case CMD_SW_ERR:
|
||||
cmd->gimbal.mode = GIMBAL_MODE_RELAX;
|
||||
cmd->shoot.mode = SHOOT_MODE_RELAX;
|
||||
}
|
||||
/* 将操纵杆的对应值转换为底盘的控制向量和云台变化的欧拉角 */
|
||||
cmd->chassis.ctrl_vec.vx = rc->ch_l_x;
|
||||
cmd->chassis.ctrl_vec.vy = rc->ch_l_y;
|
||||
cmd->gimbal.delta_eulr.yaw = rc->ch_r_x * dt_sec * cmd->param->sens_rc;
|
||||
cmd->gimbal.delta_eulr.pit = rc->ch_r_y * dt_sec * cmd->param->sens_rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief rc失控时机器人恢复放松模式
|
||||
*
|
||||
* @param cmd 主结构体
|
||||
*/
|
||||
static void CMD_RcLostLogic(CMD_t *cmd) {
|
||||
/* 机器人底盘、云台、射击运行模式恢复至放松模式 */
|
||||
cmd->chassis.mode = CHASSIS_MODE_RELAX;
|
||||
cmd->gimbal.mode = GIMBAL_MODE_RELAX;
|
||||
cmd->shoot.mode = SHOOT_MODE_RELAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化命令解析
|
||||
*
|
||||
* @param cmd 主结构体
|
||||
* @param param 参数
|
||||
* @return int8_t 0对应没有错误
|
||||
*/
|
||||
int8_t CMD_Init(CMD_t *cmd, const CMD_Params_t *param) {
|
||||
/* 指针检测 */
|
||||
if (cmd == NULL) return -1;
|
||||
if (param == NULL) return -1;
|
||||
|
||||
/* 设置机器人的命令参数,初始化控制方式为rc控制 */
|
||||
cmd->pc_ctrl = false;
|
||||
cmd->param = param;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用上位机控制指令覆盖
|
||||
*
|
||||
* @param cmd 主结构体
|
||||
* @return true 启用
|
||||
* @return false 不启用
|
||||
*/
|
||||
inline bool CMD_CheckHostOverwrite(CMD_t *cmd) { return cmd->host_overwrite; }
|
||||
|
||||
/**
|
||||
* @brief 解析命令
|
||||
*
|
||||
* @param rc 遥控器数据
|
||||
* @param cmd 命令
|
||||
* @param dt_sec 两次解析的间隔
|
||||
* @return int8_t 0对应没有错误
|
||||
*/
|
||||
int8_t CMD_ParseRc(CMD_RC_t *rc, CMD_t *cmd, float dt_sec) {
|
||||
/* 指针检测 */
|
||||
if (rc == NULL) return -1;
|
||||
if (cmd == NULL) return -1;
|
||||
|
||||
/* 在pc控制和rc控制间切换 */
|
||||
if (CMD_KeyPressedRc(rc, CMD_KEY_SHIFT) &&
|
||||
CMD_KeyPressedRc(rc, CMD_KEY_CTRL) && CMD_KeyPressedRc(rc, CMD_KEY_Q))
|
||||
cmd->pc_ctrl = true;
|
||||
|
||||
if (CMD_KeyPressedRc(rc, CMD_KEY_SHIFT) &&
|
||||
CMD_KeyPressedRc(rc, CMD_KEY_CTRL) && CMD_KeyPressedRc(rc, CMD_KEY_E))
|
||||
cmd->pc_ctrl = false;
|
||||
/*c当rc丢控时,恢复机器人至默认状态 */
|
||||
if ((rc->sw_l == CMD_SW_ERR) || (rc->sw_r == CMD_SW_ERR)) {
|
||||
CMD_RcLostLogic(cmd);
|
||||
} else {
|
||||
if (cmd->pc_ctrl) {
|
||||
CMD_PcLogic(rc, cmd, dt_sec);
|
||||
} else {
|
||||
CMD_RcLogic(rc, cmd, dt_sec);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析上位机命令
|
||||
*
|
||||
* @param host host数据
|
||||
* @param cmd 命令
|
||||
* @param dt_sec 两次解析的间隔
|
||||
* @return int8_t 0对应没有错误
|
||||
*/
|
||||
int8_t CMD_ParseHost(const CMD_Host_t *host, CMD_t *cmd, float dt_sec) {
|
||||
(void)dt_sec; /* 未使用dt_sec,消除警告 */
|
||||
/* 指针检测 */
|
||||
if (host == NULL) return -1;
|
||||
if (cmd == NULL) return -1;
|
||||
|
||||
/* 云台欧拉角设置为host相应的变化的欧拉角 */
|
||||
cmd->gimbal.delta_eulr.yaw = host->gimbal_delta.yaw;
|
||||
cmd->gimbal.delta_eulr.pit = host->gimbal_delta.pit;
|
||||
|
||||
/* host射击命令,设置不同的射击频率和弹丸初速度 */
|
||||
if (host->fire) {
|
||||
cmd->shoot.mode = SHOOT_MODE_LOADED;
|
||||
cmd->shoot.fire = true;
|
||||
} else {
|
||||
cmd->shoot.mode = SHOOT_MODE_SAFE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加向Referee发送的命令
|
||||
*
|
||||
* @param ref 命令队列
|
||||
* @param cmd 要添加的命令
|
||||
* @return int8_t 0对应没有错误
|
||||
*/
|
||||
int8_t CMD_RefereeAdd(CMD_RefereeCmd_t *ref, CMD_UI_t cmd) {
|
||||
/* 指针检测 */
|
||||
if (ref == NULL) return -1;
|
||||
/* 越界检测 */
|
||||
if (ref->counter >= CMD_REFEREE_MAX_NUM || ref->counter < 0) return -1;
|
||||
|
||||
/* 添加机器人当前行为状态到画图的命令队列中 */
|
||||
ref->cmd[ref->counter] = cmd;
|
||||
ref->counter++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
@ -1,318 +0,0 @@
|
||||
/*
|
||||
控制命令
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "component/ahrs.h"
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
#define CMD_REFEREE_MAX_NUM (3) /* Lines 16 omitted */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
/* 机器人型号 */
|
||||
typedef enum {
|
||||
ROBOT_MODEL_INFANTRY = 0, /* 步兵机器人 */
|
||||
ROBOT_MODEL_HERO, /* 英雄机器人 */
|
||||
ROBOT_MODEL_ENGINEER, /* 工程机器人 */
|
||||
ROBOT_MODEL_DRONE, /* 空中机器人 */
|
||||
ROBOT_MODEL_SENTRY, /* 哨兵机器人 */
|
||||
ROBOT_MODEL_NUM, /* 型号数量 */
|
||||
} CMD_RobotModel_t;
|
||||
|
||||
/* 底盘运行模式 */
|
||||
typedef enum {
|
||||
CHASSIS_MODE_RELAX, /* 放松模式,电机不输出。一般情况底盘初始化之后的模式 */
|
||||
CHASSIS_MODE_BREAK, /* 刹车模式,电机闭环控制保持静止。用于机器人停止状态 */
|
||||
CHASSIS_MODE_FOLLOW_GIMBAL, /* 通过闭环控制使车头方向跟随云台 */
|
||||
CHASSIS_MODE_FOLLOW_GIMBAL_35, /* 通过闭环控制使车头方向35度跟随云台 */
|
||||
CHASSIS_MODE_ROTOR, /* 小陀螺模式,通过闭环控制使底盘不停旋转 */
|
||||
CHASSIS_MODE_INDENPENDENT, /* 独立模式。底盘运行不受云台影响 */
|
||||
CHASSIS_MODE_OPEN, /* 开环模式。底盘运行不受PID控制,直接输出到电机 */
|
||||
} CMD_ChassisMode_t;
|
||||
|
||||
/* 云台运行模式 */
|
||||
typedef enum {
|
||||
GIMBAL_MODE_RELAX, /* 放松模式,电机不输出。一般情况云台初始化之后的模式 */
|
||||
GIMBAL_MODE_ABSOLUTE, /* 绝对坐标系控制,控制在空间内的绝对姿态 */
|
||||
GIMBAL_MODE_RELATIVE, /* 相对坐标系控制,控制相对于底盘的姿态 */
|
||||
} CMD_GimbalMode_t;
|
||||
|
||||
/* 射击运行模式 */
|
||||
typedef enum {
|
||||
SHOOT_MODE_RELAX, /* 放松模式,电机不输出 */
|
||||
SHOOT_MODE_SAFE, /* 保险模式,电机闭环控制保持静止 */
|
||||
SHOOT_MODE_LOADED, /* 上膛模式,摩擦轮开启。随时准备开火 */
|
||||
} CMD_ShootMode_t;
|
||||
|
||||
typedef enum {
|
||||
FIRE_MODE_SINGLE, /* 单发开火模式 */
|
||||
FIRE_MODE_BURST, /* N连发开火模式 */
|
||||
FIRE_MODE_CONT, /* 持续开火模式 */
|
||||
FIRE_MODE_NUM,
|
||||
} CMD_FireMode_t;
|
||||
|
||||
/* 小陀螺转动模式 */
|
||||
typedef enum {
|
||||
ROTOR_MODE_CW, /* 顺时针转动 */
|
||||
ROTOR_MODE_CCW, /* 逆时针转动 */
|
||||
ROTOR_MODE_RAND, /* 随机转动 */
|
||||
} CMD_RotorMode_t;
|
||||
|
||||
/* 底盘控制命令 */
|
||||
typedef struct {
|
||||
CMD_ChassisMode_t mode; /* 底盘运行模式 */
|
||||
CMD_RotorMode_t mode_rotor; /* 小陀螺转动模式 */
|
||||
MoveVector_t ctrl_vec; /* 底盘控制向量 */
|
||||
} CMD_ChassisCmd_t;
|
||||
|
||||
/* 云台控制命令 */
|
||||
typedef struct {
|
||||
CMD_GimbalMode_t mode; /* 云台运行模式 */
|
||||
AHRS_Eulr_t delta_eulr; /* 欧拉角变化角度 */
|
||||
} CMD_GimbalCmd_t;
|
||||
|
||||
/* 射击控制命令 */
|
||||
typedef struct {
|
||||
CMD_ShootMode_t mode; /* 射击运行模式 */
|
||||
CMD_FireMode_t fire_mode; /* 开火模式 */
|
||||
bool fire; /*开火*/
|
||||
bool cover_open; /* 弹舱盖开关 */
|
||||
bool reverse_trig; /* 拨弹电机状态 */
|
||||
} CMD_ShootCmd_t;
|
||||
|
||||
/* 拨杆位置 */
|
||||
typedef enum {
|
||||
CMD_SW_ERR = 0,
|
||||
CMD_SW_UP = 1,
|
||||
CMD_SW_MID = 3,
|
||||
CMD_SW_DOWN = 2,
|
||||
} CMD_SwitchPos_t;
|
||||
|
||||
/* 键盘按键值 */
|
||||
typedef enum {
|
||||
CMD_KEY_W = 0,
|
||||
CMD_KEY_S,
|
||||
CMD_KEY_A,
|
||||
CMD_KEY_D,
|
||||
CMD_KEY_SHIFT,
|
||||
CMD_KEY_CTRL,
|
||||
CMD_KEY_Q,
|
||||
CMD_KEY_E,
|
||||
CMD_KEY_R,
|
||||
CMD_KEY_F,
|
||||
CMD_KEY_G,
|
||||
CMD_KEY_Z,
|
||||
CMD_KEY_X,
|
||||
CMD_KEY_C,
|
||||
CMD_KEY_V,
|
||||
CMD_KEY_B,
|
||||
CMD_L_CLICK,
|
||||
CMD_R_CLICK,
|
||||
CMD_KEY_NUM,
|
||||
} CMD_KeyValue_t;
|
||||
|
||||
/* 行为值序列 */
|
||||
typedef enum {
|
||||
CMD_BEHAVIOR_FORE = 0, /* 向前 */
|
||||
CMD_BEHAVIOR_BACK, /* 向后 */
|
||||
CMD_BEHAVIOR_LEFT, /* 向左 */
|
||||
CMD_BEHAVIOR_RIGHT, /* 向右 */
|
||||
CMD_BEHAVIOR_ACCELERATE, /* 加速 */
|
||||
CMD_BEHAVIOR_DECELEBRATE, /* 减速 */
|
||||
CMD_BEHAVIOR_FIRE, /* 开火 */
|
||||
CMD_BEHAVIOR_FIRE_MODE, /* 切换开火模式 */
|
||||
CMD_BEHAVIOR_BUFF, /* 打符模式 */
|
||||
CMD_BEHAVIOR_AUTOAIM, /* 自瞄模式 */
|
||||
CMD_BEHAVIOR_OPENCOVER, /* 弹舱盖开关 */
|
||||
CMD_BEHAVIOR_ROTOR, /* 小陀螺模式 */
|
||||
CMD_BEHAVIOR_REVTRIG, /* 反转拨弹 */
|
||||
CMD_BEHAVIOR_FOLLOWGIMBAL35, /* 跟随云台呈35度 */
|
||||
CMD_BEHAVIOR_NUM,
|
||||
} CMD_Behavior_t;
|
||||
|
||||
typedef enum {
|
||||
CMD_ACTIVE_PRESSING, /* 按下时触发 */
|
||||
CMD_ACTIVE_RASING, /* 抬起时触发 */
|
||||
CMD_ACTIVE_PRESSED, /* 按住时触发 */
|
||||
} CMD_ActiveType_t;
|
||||
|
||||
typedef struct {
|
||||
CMD_ActiveType_t active;
|
||||
CMD_KeyValue_t key;
|
||||
} CMD_KeyMapItem_t;
|
||||
|
||||
/* 行为映射的对应按键数组 */
|
||||
typedef struct {
|
||||
CMD_KeyMapItem_t key_map[CMD_BEHAVIOR_NUM];
|
||||
} CMD_KeyMap_Params_t;
|
||||
|
||||
/* 位移灵敏度参数 */
|
||||
typedef struct {
|
||||
float move_sense; /* 移动灵敏度 */
|
||||
float move_fast_sense; /* 加速灵敏度 */
|
||||
float move_slow_sense; /* 减速灵敏度 */
|
||||
} CMD_Move_Params_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} CMD_Screen_t;
|
||||
|
||||
/* 命令参数 */
|
||||
typedef struct {
|
||||
float sens_mouse; /* 鼠标灵敏度 */
|
||||
float sens_rc; /* 遥控器摇杆灵敏度 */
|
||||
CMD_KeyMap_Params_t map; /* 按键映射行为命令 */
|
||||
CMD_Move_Params_t move; /* 位移灵敏度参数 */
|
||||
CMD_Screen_t screen; /* 屏幕分辨率参数 */
|
||||
} CMD_Params_t;
|
||||
|
||||
/* AI行为状态 */
|
||||
typedef enum {
|
||||
AI_STATUS_STOP, /* 停止状态 */
|
||||
AI_STATUS_AUTOAIM, /* 自瞄状态 */
|
||||
AI_STATUS_HITSWITCH, /* 打符状态 */
|
||||
AI_STATUS_AUTOMATIC /* 自动状态 */
|
||||
} CMD_AI_Status_t;
|
||||
|
||||
/* UI所用行为状态 */
|
||||
typedef enum {
|
||||
CMD_UI_NOTHING, /* 当前无状态 */
|
||||
CMD_UI_AUTO_AIM_START, /* 自瞄状态开启 */
|
||||
CMD_UI_AUTO_AIM_STOP, /* 自瞄状态关闭 */
|
||||
CMD_UI_HIT_SWITCH_START, /* 打符状态开启 */
|
||||
CMD_UI_HIT_SWITCH_STOP /* 打符状态关闭 */
|
||||
} CMD_UI_t;
|
||||
|
||||
/*裁判系统发送的命令*/
|
||||
typedef struct {
|
||||
CMD_UI_t cmd[CMD_REFEREE_MAX_NUM]; /* 命令数组 */
|
||||
uint8_t counter; /* 命令计数 */
|
||||
} CMD_RefereeCmd_t;
|
||||
|
||||
typedef struct {
|
||||
bool pc_ctrl; /* 是否使用键鼠控制 */
|
||||
bool host_overwrite; /* 是否Host控制 */
|
||||
uint16_t key_last; /* 上次按键键值 */
|
||||
|
||||
struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t z;
|
||||
bool l_click; /* 左键 */
|
||||
bool r_click; /* 右键 */
|
||||
} mouse_last; /* 鼠标值 */
|
||||
|
||||
CMD_AI_Status_t ai_status; /* AI状态 */
|
||||
|
||||
const CMD_Params_t *param; /* 命令参数 */
|
||||
|
||||
CMD_ChassisCmd_t chassis; /* 底盘控制命令 */
|
||||
CMD_GimbalCmd_t gimbal; /* 云台控制命令 */
|
||||
CMD_ShootCmd_t shoot; /* 射击控制命令 */
|
||||
CMD_RefereeCmd_t referee; /* 裁判系统发送命令 */
|
||||
} CMD_t;
|
||||
|
||||
typedef struct {
|
||||
float ch_l_x; /* 遥控器左侧摇杆横轴值,上为正 */
|
||||
float ch_l_y; /* 遥控器左侧摇杆纵轴值,右为正 */
|
||||
float ch_r_x; /* 遥控器右侧摇杆横轴值,上为正 */
|
||||
float ch_r_y; /* 遥控器右侧摇杆纵轴值,右为正 */
|
||||
|
||||
float ch_res; /* 第五通道值 */
|
||||
|
||||
CMD_SwitchPos_t sw_r; /* 右侧拨杆位置 */
|
||||
CMD_SwitchPos_t sw_l; /* 左侧拨杆位置 */
|
||||
|
||||
struct {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t z;
|
||||
bool l_click; /* 左键 */
|
||||
bool r_click; /* 右键 */
|
||||
} mouse; /* 鼠标值 */
|
||||
|
||||
uint16_t key; /* 按键值 */
|
||||
|
||||
uint16_t res; /* 保留,未启用 */
|
||||
} CMD_RC_t;
|
||||
|
||||
typedef struct {
|
||||
AHRS_Eulr_t gimbal_delta; /* 欧拉角的变化量 */
|
||||
|
||||
struct {
|
||||
float vx; /* x轴移动速度 */
|
||||
float vy; /* y轴移动速度 */
|
||||
float wz; /* z轴转动速度 */
|
||||
} chassis_move_vec; /* 底盘移动向量 */
|
||||
|
||||
bool fire; /* 开火状态 */
|
||||
} CMD_Host_t;
|
||||
|
||||
/**
|
||||
* @brief 解析行为命令
|
||||
*
|
||||
* @param rc 遥控器数据
|
||||
* @param cmd 主结构体
|
||||
*/
|
||||
int8_t CMD_Init(CMD_t *cmd, const CMD_Params_t *param);
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用上位机控制指令覆盖
|
||||
*
|
||||
* @param cmd 主结构体
|
||||
* @return true 启用
|
||||
* @return false 不启用
|
||||
*/
|
||||
bool CMD_CheckHostOverwrite(CMD_t *cmd);
|
||||
|
||||
/**
|
||||
* @brief 解析命令
|
||||
*
|
||||
* @param rc 遥控器数据
|
||||
* @param cmd 命令
|
||||
* @param dt_sec 两次解析的间隔
|
||||
* @return int8_t 0对应没有错误
|
||||
*/
|
||||
int8_t CMD_ParseRc(CMD_RC_t *rc, CMD_t *cmd, float dt_sec);
|
||||
|
||||
/**
|
||||
* @brief 解析上位机命令
|
||||
*
|
||||
* @param host host数据
|
||||
* @param cmd 命令
|
||||
* @param dt_sec 两次解析的间隔
|
||||
* @return int8_t 0对应没有错误
|
||||
*/
|
||||
int8_t CMD_ParseHost(const CMD_Host_t *host, CMD_t *cmd, float dt_sec);
|
||||
|
||||
/**
|
||||
* @brief 添加向Referee发送的命令
|
||||
*
|
||||
* @param ref 命令队列
|
||||
* @param cmd 要添加的命令
|
||||
* @return int8_t 0对应没有错误
|
||||
*/
|
||||
int8_t CMD_RefereeAdd(CMD_RefereeCmd_t *ref, CMD_UI_t cmd);
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -6,6 +6,12 @@ cmd:
|
||||
dependencies:
|
||||
- component/ahrs
|
||||
enabled: true
|
||||
crc16:
|
||||
dependencies: []
|
||||
enabled: true
|
||||
crc8:
|
||||
dependencies: []
|
||||
enabled: true
|
||||
filter:
|
||||
dependencies:
|
||||
- component/ahrs
|
||||
|
||||
62
User/component/crc16.c
Normal file
62
User/component/crc16.c
Normal file
@ -0,0 +1,62 @@
|
||||
#include "crc16.h"
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
static const uint16_t crc16_tab[256] = {
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48,
|
||||
0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108,
|
||||
0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb,
|
||||
0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399,
|
||||
0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e,
|
||||
0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e,
|
||||
0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd,
|
||||
0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285,
|
||||
0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44,
|
||||
0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014,
|
||||
0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5,
|
||||
0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3,
|
||||
0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862,
|
||||
0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e,
|
||||
0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1,
|
||||
0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483,
|
||||
0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50,
|
||||
0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710,
|
||||
0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7,
|
||||
0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1,
|
||||
0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72,
|
||||
0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e,
|
||||
0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf,
|
||||
0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d,
|
||||
0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c,
|
||||
0x3de3, 0x2c6a, 0x1ef1, 0x0f78};
|
||||
|
||||
static inline uint16_t CRC16_Byte(uint16_t crc, const uint8_t data) {
|
||||
return (crc >> 8) ^ crc16_tab[(crc ^ data) & 0xff];
|
||||
}
|
||||
|
||||
uint16_t CRC16_Calc(const uint8_t *buf, size_t len, uint16_t crc) {
|
||||
while (len--) crc = CRC16_Byte(crc, *buf++);
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool CRC16_Verify(const uint8_t *buf, size_t len) {
|
||||
if (len < 2) return false;
|
||||
|
||||
uint16_t expected = CRC16_Calc(buf, len - sizeof(uint16_t), CRC16_INIT);
|
||||
return expected ==
|
||||
((const uint16_t *)((const uint8_t *)buf +
|
||||
(len % 2)))[len / sizeof(uint16_t) - 1];
|
||||
}
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
30
User/component/crc16.h
Normal file
30
User/component/crc16.h
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "user_math.h"
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
#define CRC16_INIT 0XFFFF
|
||||
|
||||
uint16_t CRC16_Calc(const uint8_t *buf, size_t len, uint16_t crc);
|
||||
bool CRC16_Verify(const uint8_t *buf, size_t len);
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
52
User/component/crc8.c
Normal file
52
User/component/crc8.c
Normal file
@ -0,0 +1,52 @@
|
||||
#include "crc8.h"
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
static const uint8_t crc8_tab[256] = {
|
||||
0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 0xc2, 0x9c, 0x7e, 0x20,
|
||||
0xa3, 0xfd, 0x1f, 0x41, 0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e,
|
||||
0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc, 0x23, 0x7d, 0x9f, 0xc1,
|
||||
0x42, 0x1c, 0xfe, 0xa0, 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62,
|
||||
0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, 0x7c, 0x22, 0xc0, 0x9e,
|
||||
0x1d, 0x43, 0xa1, 0xff, 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5,
|
||||
0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07, 0xdb, 0x85, 0x67, 0x39,
|
||||
0xba, 0xe4, 0x06, 0x58, 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a,
|
||||
0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, 0xa7, 0xf9, 0x1b, 0x45,
|
||||
0xc6, 0x98, 0x7a, 0x24, 0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b,
|
||||
0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9, 0x8c, 0xd2, 0x30, 0x6e,
|
||||
0xed, 0xb3, 0x51, 0x0f, 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd,
|
||||
0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92, 0xd3, 0x8d, 0x6f, 0x31,
|
||||
0xb2, 0xec, 0x0e, 0x50, 0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c,
|
||||
0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee, 0x32, 0x6c, 0x8e, 0xd0,
|
||||
0x53, 0x0d, 0xef, 0xb1, 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73,
|
||||
0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, 0x08, 0x56, 0xb4, 0xea,
|
||||
0x69, 0x37, 0xd5, 0x8b, 0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4,
|
||||
0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16, 0xe9, 0xb7, 0x55, 0x0b,
|
||||
0x88, 0xd6, 0x34, 0x6a, 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8,
|
||||
0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, 0xb6, 0xe8, 0x0a, 0x54,
|
||||
0xd7, 0x89, 0x6b, 0x35,
|
||||
};
|
||||
|
||||
uint8_t CRC8_Calc(const uint8_t *buf, size_t len, uint8_t crc) {
|
||||
/* loop over the buffer data */
|
||||
while (len-- > 0) crc = crc8_tab[(crc ^ *buf++) & 0xff];
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool CRC8_Verify(const uint8_t *buf, size_t len) {
|
||||
if (len < 2) return false;
|
||||
|
||||
uint8_t expected = CRC8_Calc(buf, len - sizeof(uint8_t), CRC8_INIT);
|
||||
return expected == buf[len - sizeof(uint8_t)];
|
||||
}
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
30
User/component/crc8.h
Normal file
30
User/component/crc8.h
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
#define CRC8_INIT 0xFF
|
||||
|
||||
uint8_t CRC8_Calc(const uint8_t *buf, size_t len, uint8_t crc);
|
||||
bool CRC8_Verify(const uint8_t *buf, size_t len);
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,70 +1,32 @@
|
||||
/*
|
||||
* LQR控制器实现
|
||||
* LQR控制器实现:单腿建模
|
||||
*/
|
||||
|
||||
#include "lqr.h"
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Private macros ----------------------------------------------------------- */
|
||||
#define LQR_LIMIT(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
|
||||
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
|
||||
/* 从MATLAB仿真get_k.m得到的默认增益矩阵多项式拟合系数 */
|
||||
/* 这些系数是通过对不同腿长的LQR增益进行3阶多项式拟合得到的 */
|
||||
static LQR_GainMatrix_t default_gain_matrix = {
|
||||
/* K矩阵第一行 - 轮毂力矩T的增益系数 */
|
||||
.k11_coeff = {0.0f, -2845.3f, 1899.4f, -123.8f}, /* theta */
|
||||
.k12_coeff = {0.0f, -89.7f, 61.2f, -4.8f}, /* d_theta */
|
||||
.k13_coeff = {0.0f, 5479.2f, -3298.6f, 489.8f}, /* x */
|
||||
.k14_coeff = {0.0f, 312.4f, -178.9f, 34.2f}, /* d_x */
|
||||
.k15_coeff = {0.0f, -31250.0f, 18750.0f, -3125.0f}, /* phi */
|
||||
.k16_coeff = {0.0f, -89.7f, 61.2f, -4.8f}, /* d_phi */
|
||||
|
||||
/* K矩阵第二行 - 髋关节力矩Tp的增益系数 */
|
||||
.k21_coeff = {0.0f, 486.1f, -324.1f, 21.6f}, /* theta */
|
||||
.k22_coeff = {0.0f, 15.3f, -10.4f, 0.8f}, /* d_theta */
|
||||
.k23_coeff = {0.0f, -935.4f, 562.2f, -83.5f}, /* x */
|
||||
.k24_coeff = {0.0f, -53.3f, 30.5f, -5.8f}, /* d_x */
|
||||
.k25_coeff = {0.0f, 5333.3f, -3200.0f, 533.3f}, /* phi */
|
||||
.k26_coeff = {0.0f, 15.3f, -10.4f, 0.8f}, /* d_phi */
|
||||
};
|
||||
|
||||
/* Private function prototypes ---------------------------------------------- */
|
||||
static int8_t LQR_CalculateErrorState(LQR_Controller_t *lqr);
|
||||
static int8_t LQR_ApplyControlLimits(LQR_Controller_t *lqr);
|
||||
static int8_t LQR_CalculateErrorState(LQR_t *lqr);
|
||||
static float LQR_PolynomialCalc(const float *coeff, float leg_length);
|
||||
|
||||
/* Public functions --------------------------------------------------------- */
|
||||
|
||||
int8_t LQR_Init(LQR_Controller_t *lqr, float max_wheel_torque, float max_joint_torque) {
|
||||
if (lqr == NULL) {
|
||||
int8_t LQR_Init(LQR_t *lqr, LQR_GainMatrix_t *gain_matrix) {
|
||||
if (lqr == NULL || gain_matrix == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 清零结构体 */
|
||||
memset(lqr, 0, sizeof(LQR_Controller_t));
|
||||
memset(lqr, 0, sizeof(LQR_t));
|
||||
|
||||
/* 设置控制限制 */
|
||||
lqr->param.max_wheel_torque = max_wheel_torque;
|
||||
lqr->param.max_joint_torque = max_joint_torque;
|
||||
|
||||
/* 设置默认系统物理参数(从MATLAB仿真get_k_length.m获取) */
|
||||
lqr->param.R = 0.072f; /* 驱动轮半径 */
|
||||
lqr->param.l = 0.03f; /* 机体重心到转轴距离 */
|
||||
lqr->param.mw = 0.60f; /* 驱动轮质量 */
|
||||
lqr->param.mp = 1.8f; /* 摆杆质量 */
|
||||
lqr->param.M = 1.8f; /* 机体质量 */
|
||||
lqr->param.g = 9.8f; /* 重力加速度 */
|
||||
|
||||
/* 计算转动惯量 */
|
||||
lqr->param.Iw = lqr->param.mw * lqr->param.R * lqr->param.R;
|
||||
lqr->param.IM = lqr->param.M * (0.3f * 0.3f + 0.12f * 0.12f) / 12.0f;
|
||||
|
||||
/* 设置默认增益矩阵 */
|
||||
lqr->gain_matrix = &default_gain_matrix;
|
||||
|
||||
/* 设置默认目标状态(平衡状态) */
|
||||
memset(&lqr->param.target_state, 0, sizeof(LQR_State_t));
|
||||
/* 设置增益矩阵 */
|
||||
lqr->gain_matrix = gain_matrix;
|
||||
|
||||
/* 初始化当前腿长为中等值 */
|
||||
lqr->current_leg_length = 0.25f;
|
||||
@ -77,41 +39,30 @@ int8_t LQR_Init(LQR_Controller_t *lqr, float max_wheel_torque, float max_joint_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t LQR_SetGainMatrix(LQR_Controller_t *lqr, LQR_GainMatrix_t *gain_matrix) {
|
||||
if (lqr == NULL || gain_matrix == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
lqr->gain_matrix = gain_matrix;
|
||||
|
||||
/* 重新计算增益矩阵 */
|
||||
return LQR_CalculateGainMatrix(lqr, lqr->current_leg_length);
|
||||
}
|
||||
|
||||
int8_t LQR_UpdateState(LQR_Controller_t *lqr, const LQR_State_t *state) {
|
||||
if (lqr == NULL || state == NULL) {
|
||||
int8_t LQR_UpdateState(LQR_t *lqr, const LQR_State_t *current_state) {
|
||||
if (lqr == NULL || current_state == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 更新当前状态 */
|
||||
lqr->current_state = *state;
|
||||
lqr->current_state = *current_state;
|
||||
|
||||
/* 计算状态误差 */
|
||||
return LQR_CalculateErrorState(lqr);
|
||||
}
|
||||
|
||||
int8_t LQR_SetTargetState(LQR_Controller_t *lqr, const LQR_State_t *target_state) {
|
||||
int8_t LQR_SetTargetState(LQR_t *lqr, const LQR_State_t *target_state) {
|
||||
if (lqr == NULL || target_state == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
lqr->param.target_state = *target_state;
|
||||
lqr->target_state = *target_state;
|
||||
|
||||
/* 重新计算状态误差 */
|
||||
return LQR_CalculateErrorState(lqr);
|
||||
}
|
||||
|
||||
int8_t LQR_CalculateGainMatrix(LQR_Controller_t *lqr, float leg_length) {
|
||||
int8_t LQR_CalculateGainMatrix(LQR_t *lqr, float leg_length) {
|
||||
if (lqr == NULL || lqr->gain_matrix == NULL) {
|
||||
return -1;
|
||||
}
|
||||
@ -120,14 +71,6 @@ int8_t LQR_CalculateGainMatrix(LQR_Controller_t *lqr, float leg_length) {
|
||||
leg_length = LQR_LIMIT(leg_length, 0.1f, 0.4f);
|
||||
lqr->current_leg_length = leg_length;
|
||||
|
||||
/* 更新与腿长相关的物理参数 */
|
||||
lqr->param.L = leg_length / 2.0f; /* 摆杆重心到驱动轮轴距离 */
|
||||
lqr->param.LM = leg_length / 2.0f; /* 摆杆重心到其转轴距离 */
|
||||
|
||||
/* 计算摆杆转动惯量 */
|
||||
float leg_total_length = lqr->param.L + lqr->param.LM;
|
||||
lqr->param.Ip = lqr->param.mp * (leg_total_length * leg_total_length + 0.05f * 0.05f) / 12.0f;
|
||||
|
||||
/* 使用多项式拟合计算当前增益矩阵 */
|
||||
lqr->K_matrix[0][0] = LQR_PolynomialCalc(lqr->gain_matrix->k11_coeff, leg_length); /* K11: theta */
|
||||
lqr->K_matrix[0][1] = LQR_PolynomialCalc(lqr->gain_matrix->k12_coeff, leg_length); /* K12: d_theta */
|
||||
@ -146,7 +89,7 @@ int8_t LQR_CalculateGainMatrix(LQR_Controller_t *lqr, float leg_length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t LQR_Control(LQR_Controller_t *lqr) {
|
||||
int8_t LQR_Control(LQR_t *lqr) {
|
||||
if (lqr == NULL || !lqr->initialized) {
|
||||
return -1;
|
||||
}
|
||||
@ -175,38 +118,42 @@ int8_t LQR_Control(LQR_Controller_t *lqr) {
|
||||
lqr->K_matrix[1][5] * lqr->error_state.d_phi
|
||||
);
|
||||
|
||||
/* 应用控制限制 */
|
||||
return LQR_ApplyControlLimits(lqr);
|
||||
}
|
||||
|
||||
int8_t LQR_GetOutput(LQR_Controller_t *lqr, LQR_Input_t *output) {
|
||||
if (lqr == NULL || output == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*output = lqr->control_output;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t LQR_Reset(LQR_Controller_t *lqr) {
|
||||
int8_t LQR_Reset(LQR_t *lqr) {
|
||||
if (lqr == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 清零状态和输出 */
|
||||
memset(&lqr->current_state, 0, sizeof(LQR_State_t));
|
||||
memset(&lqr->target_state, 0, sizeof(LQR_State_t));
|
||||
memset(&lqr->error_state, 0, sizeof(LQR_State_t));
|
||||
memset(&lqr->control_output, 0, sizeof(LQR_Input_t));
|
||||
memset(&lqr->param.target_state, 0, sizeof(LQR_State_t));
|
||||
|
||||
/* 重置限幅标志 */
|
||||
lqr->wheel_torque_limited = false;
|
||||
lqr->joint_torque_limited = false;
|
||||
memset(&lqr->control_output, 0, sizeof(LQR_Output_t));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
float LQR_PolynomialCalc(const float *coeff, float leg_length) {
|
||||
/* Private functions -------------------------------------------------------- */
|
||||
|
||||
static int8_t LQR_CalculateErrorState(LQR_t *lqr) {
|
||||
if (lqr == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 计算状态误差 */
|
||||
lqr->error_state.theta = lqr->current_state.theta - lqr->target_state.theta;
|
||||
lqr->error_state.d_theta = lqr->current_state.d_theta - lqr->target_state.d_theta;
|
||||
lqr->error_state.x = lqr->current_state.x - lqr->target_state.x;
|
||||
lqr->error_state.d_x = lqr->current_state.d_x - lqr->target_state.d_x;
|
||||
lqr->error_state.phi = lqr->current_state.phi - lqr->target_state.phi;
|
||||
lqr->error_state.d_phi = lqr->current_state.d_phi - lqr->target_state.d_phi;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static float LQR_PolynomialCalc(const float *coeff, float leg_length) {
|
||||
if (coeff == NULL) {
|
||||
return 0.0f;
|
||||
}
|
||||
@ -218,49 +165,3 @@ float LQR_PolynomialCalc(const float *coeff, float leg_length) {
|
||||
|
||||
return coeff[0] * L3 + coeff[1] * L2 + coeff[2] * L + coeff[3];
|
||||
}
|
||||
|
||||
/* Private functions -------------------------------------------------------- */
|
||||
|
||||
static int8_t LQR_CalculateErrorState(LQR_Controller_t *lqr) {
|
||||
if (lqr == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 计算状态误差 */
|
||||
lqr->error_state.theta = lqr->current_state.theta - lqr->param.target_state.theta;
|
||||
lqr->error_state.d_theta = lqr->current_state.d_theta - lqr->param.target_state.d_theta;
|
||||
lqr->error_state.x = lqr->current_state.x - lqr->param.target_state.x;
|
||||
lqr->error_state.d_x = lqr->current_state.d_x - lqr->param.target_state.d_x;
|
||||
lqr->error_state.phi = lqr->current_state.phi - lqr->param.target_state.phi;
|
||||
lqr->error_state.d_phi = lqr->current_state.d_phi - lqr->param.target_state.d_phi;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int8_t LQR_ApplyControlLimits(LQR_Controller_t *lqr) {
|
||||
if (lqr == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 重置限幅标志 */
|
||||
lqr->wheel_torque_limited = false;
|
||||
lqr->joint_torque_limited = false;
|
||||
|
||||
/* 限制轮毂力矩 */
|
||||
if (fabsf(lqr->control_output.T) > lqr->param.max_wheel_torque) {
|
||||
lqr->control_output.T = LQR_LIMIT(lqr->control_output.T,
|
||||
-lqr->param.max_wheel_torque,
|
||||
lqr->param.max_wheel_torque);
|
||||
lqr->wheel_torque_limited = true;
|
||||
}
|
||||
|
||||
/* 限制髋关节力矩 */
|
||||
if (fabsf(lqr->control_output.Tp) > lqr->param.max_joint_torque) {
|
||||
lqr->control_output.Tp = LQR_LIMIT(lqr->control_output.Tp,
|
||||
-lqr->param.max_joint_torque,
|
||||
lqr->param.max_joint_torque);
|
||||
lqr->joint_torque_limited = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* LQR控制器
|
||||
* 用于轮腿平衡机器人的线性二次调节器控制
|
||||
* 用于轮腿平衡机器人的线性二次调节器控制:单腿建模
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@ -21,16 +21,6 @@ extern "C" {
|
||||
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief LQR状态向量
|
||||
* 状态定义:
|
||||
* theta: 摆杆与竖直方向夹角 (rad)
|
||||
* d_theta: 摆杆角速度 (rad/s)
|
||||
* x: 驱动轮位移 (m)
|
||||
* d_x: 驱动轮速度 (m/s)
|
||||
* phi: 机体与水平夹角 (rad)
|
||||
* d_phi: 机体角速度 (rad/s)
|
||||
*/
|
||||
typedef struct {
|
||||
float theta; /* 摆杆角度 */
|
||||
float d_theta; /* 摆杆角速度 */
|
||||
@ -40,19 +30,11 @@ typedef struct {
|
||||
float d_phi; /* 机体俯仰角速度 */
|
||||
} LQR_State_t;
|
||||
|
||||
/**
|
||||
* @brief LQR控制输入向量
|
||||
*/
|
||||
typedef struct {
|
||||
float T; /* 轮毂力矩 (N·m) */
|
||||
float Tp; /* 髋关节力矩 (N·m) */
|
||||
} LQR_Input_t;
|
||||
} LQR_Output_t;
|
||||
|
||||
/**
|
||||
* @brief LQR增益矩阵参数
|
||||
* K矩阵的每个元素的多项式拟合系数
|
||||
* K(leg_length) = a[0]*L^3 + a[1]*L^2 + a[2]*L + a[3]
|
||||
*/
|
||||
typedef struct {
|
||||
/* K矩阵第一行(轮毂力矩T对应的增益) */
|
||||
float k11_coeff[LQR_POLY_ORDER]; /* K(1,1): theta */
|
||||
@ -71,41 +53,18 @@ typedef struct {
|
||||
float k26_coeff[LQR_POLY_ORDER]; /* K(2,6): d_phi */
|
||||
} LQR_GainMatrix_t;
|
||||
|
||||
/**
|
||||
* @brief LQR控制器参数
|
||||
*/
|
||||
typedef struct {
|
||||
/* 系统物理参数 */
|
||||
float R; /* 驱动轮半径 (m) */
|
||||
float L; /* 摆杆重心到驱动轮轴距离 (m) */
|
||||
float LM; /* 摆杆重心到其转轴距离 (m) */
|
||||
float l; /* 机体重心到其转轴距离 (m) */
|
||||
float mw; /* 驱动轮质量 (kg) */
|
||||
float mp; /* 摆杆质量 (kg) */
|
||||
float M; /* 机体质量 (kg) */
|
||||
float Iw; /* 驱动轮转动惯量 (kg·m²) */
|
||||
float Ip; /* 摆杆绕质心转动惯量 (kg·m²) */
|
||||
float IM; /* 机体绕质心转动惯量 (kg·m²) */
|
||||
float g; /* 重力加速度 (m/s²) */
|
||||
|
||||
/* 控制限制 */
|
||||
float max_wheel_torque; /* 轮毂电机最大力矩 (N·m) */
|
||||
float max_joint_torque; /* 关节电机最大力矩 (N·m) */
|
||||
|
||||
/* 目标状态 */
|
||||
LQR_State_t target_state; /* 目标状态 */
|
||||
} LQR_Param_t;
|
||||
|
||||
/**
|
||||
* @brief LQR控制器主结构体
|
||||
*/
|
||||
typedef struct {
|
||||
LQR_Param_t param; /* 控制器参数 */
|
||||
LQR_GainMatrix_t *gain_matrix; /* 增益矩阵参数指针 */
|
||||
|
||||
LQR_State_t current_state; /* 当前状态 */
|
||||
LQR_State_t target_state; /* 目标状态 */
|
||||
LQR_State_t error_state; /* 状态误差 */
|
||||
LQR_Input_t control_output; /* 控制输出 */
|
||||
|
||||
LQR_Output_t control_output; /* 控制输出 */
|
||||
|
||||
/* 运行时变量 */
|
||||
float current_leg_length; /* 当前腿长 */
|
||||
@ -113,10 +72,7 @@ typedef struct {
|
||||
|
||||
bool initialized; /* 初始化标志 */
|
||||
|
||||
/* 限幅标志 */
|
||||
bool wheel_torque_limited; /* 轮毂力矩是否被限幅 */
|
||||
bool joint_torque_limited; /* 关节力矩是否被限幅 */
|
||||
} LQR_Controller_t;
|
||||
} LQR_t;
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
|
||||
@ -124,20 +80,11 @@ typedef struct {
|
||||
* @brief 初始化LQR控制器
|
||||
*
|
||||
* @param lqr LQR控制器结构体指针
|
||||
* @param max_wheel_torque 轮毂电机最大力矩
|
||||
* @param max_joint_torque 关节电机最大力矩
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_Init(LQR_Controller_t *lqr, float max_wheel_torque, float max_joint_torque);
|
||||
|
||||
/**
|
||||
* @brief 设置LQR增益矩阵参数
|
||||
*
|
||||
* @param lqr LQR控制器结构体指针
|
||||
* @param gain_matrix 增益矩阵参数指针
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_SetGainMatrix(LQR_Controller_t *lqr, LQR_GainMatrix_t *gain_matrix);
|
||||
int8_t LQR_Init(LQR_t *lqr, LQR_GainMatrix_t *gain_matrix);
|
||||
|
||||
|
||||
/**
|
||||
* @brief 更新当前状态
|
||||
@ -146,7 +93,7 @@ int8_t LQR_SetGainMatrix(LQR_Controller_t *lqr, LQR_GainMatrix_t *gain_matrix);
|
||||
* @param state 当前状态
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_UpdateState(LQR_Controller_t *lqr, const LQR_State_t *state);
|
||||
int8_t LQR_UpdateState(LQR_t *lqr, const LQR_State_t *current_state);
|
||||
|
||||
/**
|
||||
* @brief 设置目标状态
|
||||
@ -155,7 +102,7 @@ int8_t LQR_UpdateState(LQR_Controller_t *lqr, const LQR_State_t *state);
|
||||
* @param target_state 目标状态
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_SetTargetState(LQR_Controller_t *lqr, const LQR_State_t *target_state);
|
||||
int8_t LQR_SetTargetState(LQR_t *lqr, const LQR_State_t *target_state);
|
||||
|
||||
/**
|
||||
* @brief 根据当前腿长计算增益矩阵
|
||||
@ -164,7 +111,7 @@ int8_t LQR_SetTargetState(LQR_Controller_t *lqr, const LQR_State_t *target_state
|
||||
* @param leg_length 当前腿长 (m)
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_CalculateGainMatrix(LQR_Controller_t *lqr, float leg_length);
|
||||
int8_t LQR_CalculateGainMatrix(LQR_t *lqr, float leg_length);
|
||||
|
||||
/**
|
||||
* @brief 执行LQR控制计算
|
||||
@ -172,16 +119,7 @@ int8_t LQR_CalculateGainMatrix(LQR_Controller_t *lqr, float leg_length);
|
||||
* @param lqr LQR控制器结构体指针
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_Control(LQR_Controller_t *lqr);
|
||||
|
||||
/**
|
||||
* @brief 获取控制输出
|
||||
*
|
||||
* @param lqr LQR控制器结构体指针
|
||||
* @param output 输出控制指令指针
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_GetOutput(LQR_Controller_t *lqr, LQR_Input_t *output);
|
||||
int8_t LQR_Control(LQR_t *lqr);
|
||||
|
||||
/**
|
||||
* @brief 重置LQR控制器
|
||||
@ -189,16 +127,7 @@ int8_t LQR_GetOutput(LQR_Controller_t *lqr, LQR_Input_t *output);
|
||||
* @param lqr LQR控制器结构体指针
|
||||
* @return int8_t 0-成功, 其他-失败
|
||||
*/
|
||||
int8_t LQR_Reset(LQR_Controller_t *lqr);
|
||||
|
||||
/**
|
||||
* @brief 根据多项式系数计算增益值
|
||||
*
|
||||
* @param coeff 多项式系数数组
|
||||
* @param leg_length 腿长
|
||||
* @return float 增益值
|
||||
*/
|
||||
float LQR_PolynomialCalc(const float *coeff, float leg_length);
|
||||
int8_t LQR_Reset(LQR_t *lqr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
266
User/component/speed_planner.c
Normal file
266
User/component/speed_planner.c
Normal file
@ -0,0 +1,266 @@
|
||||
#include "component/speed_planner.h"
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 浮点数比较容差 */
|
||||
#define FLOAT_EPSILON 1e-6f
|
||||
|
||||
/* 限制函数 */
|
||||
static float SpeedPlanner_Limit(float value, float min, float max) {
|
||||
if (value > max) return max;
|
||||
if (value < min) return min;
|
||||
return value;
|
||||
}
|
||||
|
||||
/* 符号函数 */
|
||||
static float SpeedPlanner_Sign(float value) {
|
||||
if (value > FLOAT_EPSILON) return 1.0f;
|
||||
if (value < -FLOAT_EPSILON) return -1.0f;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
/* 获取速度符号,用于判断加速/减速方向 */
|
||||
__attribute__((unused)) static float SpeedPlanner_GetVelocitySign(float velocity) {
|
||||
return SpeedPlanner_Sign(velocity);
|
||||
}
|
||||
|
||||
int8_t SpeedPlanner_Init(SpeedPlanner_t *planner, SpeedPlanner_Param_t *param, float control_freq) {
|
||||
if (planner == NULL || param == NULL || control_freq <= 0.0f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 参数检查 */
|
||||
if (param->max_velocity <= 0.0f || param->max_acceleration <= 0.0f ||
|
||||
param->max_deceleration <= 0.0f || param->position_error_limit <= 0.0f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 复制参数 */
|
||||
planner->param = *param;
|
||||
|
||||
/* 计算控制周期 */
|
||||
planner->dt = 1.0f / control_freq;
|
||||
|
||||
/* 初始化状态 */
|
||||
SpeedPlanner_Reset(planner);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t SpeedPlanner_Reset(SpeedPlanner_t *planner) {
|
||||
if (planner == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
planner->state = SPEED_PLANNER_IDLE;
|
||||
planner->current_velocity = 0.0f;
|
||||
planner->current_position = 0.0f;
|
||||
planner->target_velocity = 0.0f;
|
||||
planner->target_position = 0.0f;
|
||||
planner->planned_velocity = 0.0f;
|
||||
planner->planned_acceleration = 0.0f;
|
||||
planner->last_target_velocity = 0.0f;
|
||||
planner->position_error = 0.0f;
|
||||
planner->ramp_start_velocity = 0.0f;
|
||||
planner->ramp_end_velocity = 0.0f;
|
||||
planner->ramp_duration = 0.0f;
|
||||
planner->ramp_time = 0.0f;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t SpeedPlanner_UpdateState(SpeedPlanner_t *planner, float current_velocity, float current_position) {
|
||||
if (planner == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
planner->current_velocity = current_velocity;
|
||||
planner->current_position = current_position;
|
||||
planner->position_error = planner->target_position - planner->current_position;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t SpeedPlanner_SetTargetVelocity(SpeedPlanner_t *planner, float target_velocity) {
|
||||
if (planner == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 限制目标速度在允许范围内 */
|
||||
planner->target_velocity = SpeedPlanner_Limit(target_velocity,
|
||||
-planner->param.max_velocity,
|
||||
planner->param.max_velocity);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t SpeedPlanner_SetTargetPosition(SpeedPlanner_t *planner, float target_position) {
|
||||
if (planner == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
planner->target_position = target_position;
|
||||
planner->position_error = planner->target_position - planner->current_position;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 检查目标速度是否改变 */
|
||||
static int8_t SpeedPlanner_IsTargetChanged(SpeedPlanner_t *planner) {
|
||||
return (fabsf(planner->target_velocity - planner->last_target_velocity) > planner->param.velocity_tolerance);
|
||||
}
|
||||
|
||||
/* 开始新的斜坡规划 */
|
||||
static void SpeedPlanner_StartRamp(SpeedPlanner_t *planner) {
|
||||
planner->ramp_start_velocity = planner->current_velocity;
|
||||
planner->ramp_end_velocity = planner->target_velocity;
|
||||
|
||||
/* 计算需要的加速度和时间 */
|
||||
float velocity_diff = planner->ramp_end_velocity - planner->ramp_start_velocity;
|
||||
float required_acceleration = fabsf(velocity_diff);
|
||||
|
||||
/* 选择合适的加速度(加速或减速) */
|
||||
float max_accel = (velocity_diff >= 0) ? planner->param.max_acceleration : planner->param.max_deceleration;
|
||||
|
||||
if (required_acceleration > max_accel) {
|
||||
required_acceleration = max_accel;
|
||||
}
|
||||
|
||||
/* 计算斜坡时间 */
|
||||
if (required_acceleration > FLOAT_EPSILON) {
|
||||
planner->ramp_duration = fabsf(velocity_diff) / required_acceleration;
|
||||
} else {
|
||||
planner->ramp_duration = 0.0f;
|
||||
}
|
||||
|
||||
planner->ramp_time = 0.0f;
|
||||
|
||||
/* 设置状态 */
|
||||
if (fabsf(velocity_diff) < planner->param.velocity_tolerance) {
|
||||
planner->state = SPEED_PLANNER_FINISHED;
|
||||
} else if (velocity_diff > 0) {
|
||||
planner->state = SPEED_PLANNER_ACCELERATING;
|
||||
} else {
|
||||
planner->state = SPEED_PLANNER_DECELERATING;
|
||||
}
|
||||
}
|
||||
|
||||
/* 执行斜坡插值 */
|
||||
static float SpeedPlanner_RampInterpolation(SpeedPlanner_t *planner) {
|
||||
if (planner->ramp_duration <= FLOAT_EPSILON) {
|
||||
return planner->ramp_end_velocity;
|
||||
}
|
||||
|
||||
/* 线性插值 */
|
||||
float progress = planner->ramp_time / planner->ramp_duration;
|
||||
progress = SpeedPlanner_Limit(progress, 0.0f, 1.0f);
|
||||
|
||||
return planner->ramp_start_velocity +
|
||||
(planner->ramp_end_velocity - planner->ramp_start_velocity) * progress;
|
||||
}
|
||||
|
||||
int8_t SpeedPlanner_Update(SpeedPlanner_t *planner) {
|
||||
if (planner == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 检查位置误差是否超限 */
|
||||
if (fabsf(planner->position_error) > planner->param.position_error_limit) {
|
||||
/* 紧急停止:设置目标速度为0 */
|
||||
planner->target_velocity = 0.0f;
|
||||
}
|
||||
|
||||
/* 检查目标是否改变 */
|
||||
if (SpeedPlanner_IsTargetChanged(planner) || planner->state == SPEED_PLANNER_IDLE) {
|
||||
SpeedPlanner_StartRamp(planner);
|
||||
planner->last_target_velocity = planner->target_velocity;
|
||||
}
|
||||
|
||||
/* 根据当前状态执行规划 */
|
||||
switch (planner->state) {
|
||||
case SPEED_PLANNER_IDLE:
|
||||
planner->planned_velocity = planner->current_velocity;
|
||||
planner->planned_acceleration = 0.0f;
|
||||
break;
|
||||
|
||||
case SPEED_PLANNER_ACCELERATING:
|
||||
case SPEED_PLANNER_DECELERATING:
|
||||
/* 执行斜坡规划 */
|
||||
planner->planned_velocity = SpeedPlanner_RampInterpolation(planner);
|
||||
|
||||
/* 计算规划加速度 */
|
||||
if (planner->dt > FLOAT_EPSILON) {
|
||||
planner->planned_acceleration = (planner->planned_velocity - planner->current_velocity) / planner->dt;
|
||||
} else {
|
||||
planner->planned_acceleration = 0.0f;
|
||||
}
|
||||
|
||||
/* 限制加速度 */
|
||||
float max_accel = (planner->state == SPEED_PLANNER_ACCELERATING) ?
|
||||
planner->param.max_acceleration : planner->param.max_deceleration;
|
||||
planner->planned_acceleration = SpeedPlanner_Limit(planner->planned_acceleration, -max_accel, max_accel);
|
||||
|
||||
/* 更新斜坡时间 */
|
||||
planner->ramp_time += planner->dt;
|
||||
|
||||
/* 检查是否完成斜坡 */
|
||||
if (planner->ramp_time >= planner->ramp_duration ||
|
||||
fabsf(planner->planned_velocity - planner->ramp_end_velocity) < planner->param.velocity_tolerance) {
|
||||
planner->state = SPEED_PLANNER_FINISHED;
|
||||
planner->planned_velocity = planner->ramp_end_velocity;
|
||||
}
|
||||
break;
|
||||
|
||||
case SPEED_PLANNER_CONSTANT:
|
||||
case SPEED_PLANNER_FINISHED:
|
||||
planner->planned_velocity = planner->target_velocity;
|
||||
planner->planned_acceleration = 0.0f;
|
||||
break;
|
||||
|
||||
default:
|
||||
planner->state = SPEED_PLANNER_IDLE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 最终限制输出速度 */
|
||||
planner->planned_velocity = SpeedPlanner_Limit(planner->planned_velocity,
|
||||
-planner->param.max_velocity,
|
||||
planner->param.max_velocity);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
float SpeedPlanner_GetPlannedVelocity(SpeedPlanner_t *planner) {
|
||||
if (planner == NULL) {
|
||||
return 0.0f;
|
||||
}
|
||||
return planner->planned_velocity;
|
||||
}
|
||||
|
||||
float SpeedPlanner_GetPlannedAcceleration(SpeedPlanner_t *planner) {
|
||||
if (planner == NULL) {
|
||||
return 0.0f;
|
||||
}
|
||||
return planner->planned_acceleration;
|
||||
}
|
||||
|
||||
float SpeedPlanner_GetPositionError(SpeedPlanner_t *planner) {
|
||||
if (planner == NULL) {
|
||||
return 0.0f;
|
||||
}
|
||||
return planner->position_error;
|
||||
}
|
||||
|
||||
SpeedPlanner_State_t SpeedPlanner_GetState(SpeedPlanner_t *planner) {
|
||||
if (planner == NULL) {
|
||||
return SPEED_PLANNER_IDLE;
|
||||
}
|
||||
return planner->state;
|
||||
}
|
||||
|
||||
int8_t SpeedPlanner_IsEmergencyStop(SpeedPlanner_t *planner) {
|
||||
if (planner == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return (fabsf(planner->position_error) > planner->param.position_error_limit) ? 1 : 0;
|
||||
}
|
||||
143
User/component/speed_planner.h
Normal file
143
User/component/speed_planner.h
Normal file
@ -0,0 +1,143 @@
|
||||
#ifndef SPEED_PLANNER_H
|
||||
#define SPEED_PLANNER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 速度规划器状态枚举 */
|
||||
typedef enum {
|
||||
SPEED_PLANNER_IDLE = 0, /* 空闲状态 */
|
||||
SPEED_PLANNER_ACCELERATING, /* 加速阶段 */
|
||||
SPEED_PLANNER_CONSTANT, /* 匀速阶段 */
|
||||
SPEED_PLANNER_DECELERATING, /* 减速阶段 */
|
||||
SPEED_PLANNER_FINISHED /* 完成状态 */
|
||||
} SpeedPlanner_State_t;
|
||||
|
||||
/* 速度规划器参数结构体 */
|
||||
typedef struct {
|
||||
float max_velocity; /* 最大速度 (m/s) */
|
||||
float max_acceleration; /* 最大加速度 (m/s²) */
|
||||
float max_deceleration; /* 最大减速度 (m/s²) */
|
||||
float position_error_limit; /* 位置误差限制 (m) */
|
||||
float velocity_tolerance; /* 速度容差,用于判断是否到达目标速度 (m/s) */
|
||||
} SpeedPlanner_Param_t;
|
||||
|
||||
/* 速度规划器结构体 */
|
||||
typedef struct {
|
||||
/* 参数 */
|
||||
SpeedPlanner_Param_t param;
|
||||
|
||||
/* 状态变量 */
|
||||
SpeedPlanner_State_t state;
|
||||
float current_velocity; /* 当前速度 (m/s) */
|
||||
float current_position; /* 当前位置 (m) */
|
||||
float target_velocity; /* 目标速度 (m/s) */
|
||||
float target_position; /* 目标位置 (m) */
|
||||
|
||||
/* 规划变量 */
|
||||
float planned_velocity; /* 规划输出速度 (m/s) */
|
||||
float planned_acceleration; /* 规划输出加速度 (m/s²) */
|
||||
|
||||
/* 内部状态 */
|
||||
float last_target_velocity; /* 上次目标速度,用于检测目标变化 */
|
||||
float position_error; /* 位置误差 (目标位置 - 当前位置) */
|
||||
float dt; /* 控制周期 (s) */
|
||||
|
||||
/* 斜坡规划状态 */
|
||||
float ramp_start_velocity; /* 斜坡起始速度 */
|
||||
float ramp_end_velocity; /* 斜坡结束速度 */
|
||||
float ramp_duration; /* 斜坡持续时间 */
|
||||
float ramp_time; /* 斜坡当前时间 */
|
||||
} SpeedPlanner_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化速度规划器
|
||||
* @param planner 速度规划器指针
|
||||
* @param param 参数结构体指针
|
||||
* @param control_freq 控制频率 (Hz)
|
||||
* @return 0: 成功, -1: 失败
|
||||
*/
|
||||
int8_t SpeedPlanner_Init(SpeedPlanner_t *planner, SpeedPlanner_Param_t *param, float control_freq);
|
||||
|
||||
/**
|
||||
* @brief 更新当前状态
|
||||
* @param planner 速度规划器指针
|
||||
* @param current_velocity 当前速度 (m/s)
|
||||
* @param current_position 当前位置 (m)
|
||||
* @return 0: 成功, -1: 失败
|
||||
*/
|
||||
int8_t SpeedPlanner_UpdateState(SpeedPlanner_t *planner, float current_velocity, float current_position);
|
||||
|
||||
/**
|
||||
* @brief 设置目标速度
|
||||
* @param planner 速度规划器指针
|
||||
* @param target_velocity 目标速度 (m/s)
|
||||
* @return 0: 成功, -1: 失败
|
||||
*/
|
||||
int8_t SpeedPlanner_SetTargetVelocity(SpeedPlanner_t *planner, float target_velocity);
|
||||
|
||||
/**
|
||||
* @brief 设置目标位置
|
||||
* @param planner 速度规划器指针
|
||||
* @param target_position 目标位置 (m)
|
||||
* @return 0: 成功, -1: 失败
|
||||
*/
|
||||
int8_t SpeedPlanner_SetTargetPosition(SpeedPlanner_t *planner, float target_position);
|
||||
|
||||
/**
|
||||
* @brief 执行速度规划计算
|
||||
* @param planner 速度规划器指针
|
||||
* @return 0: 成功, -1: 失败
|
||||
*/
|
||||
int8_t SpeedPlanner_Update(SpeedPlanner_t *planner);
|
||||
|
||||
/**
|
||||
* @brief 获取规划的速度输出
|
||||
* @param planner 速度规划器指针
|
||||
* @return 规划的速度 (m/s)
|
||||
*/
|
||||
float SpeedPlanner_GetPlannedVelocity(SpeedPlanner_t *planner);
|
||||
|
||||
/**
|
||||
* @brief 获取规划的加速度输出
|
||||
* @param planner 速度规划器指针
|
||||
* @return 规划的加速度 (m/s²)
|
||||
*/
|
||||
float SpeedPlanner_GetPlannedAcceleration(SpeedPlanner_t *planner);
|
||||
|
||||
/**
|
||||
* @brief 获取位置误差
|
||||
* @param planner 速度规划器指针
|
||||
* @return 位置误差 (m)
|
||||
*/
|
||||
float SpeedPlanner_GetPositionError(SpeedPlanner_t *planner);
|
||||
|
||||
/**
|
||||
* @brief 获取当前状态
|
||||
* @param planner 速度规划器指针
|
||||
* @return 当前状态
|
||||
*/
|
||||
SpeedPlanner_State_t SpeedPlanner_GetState(SpeedPlanner_t *planner);
|
||||
|
||||
/**
|
||||
* @brief 重置速度规划器
|
||||
* @param planner 速度规划器指针
|
||||
* @return 0: 成功, -1: 失败
|
||||
*/
|
||||
int8_t SpeedPlanner_Reset(SpeedPlanner_t *planner);
|
||||
|
||||
/**
|
||||
* @brief 检查是否需要紧急停止(位置误差过大)
|
||||
* @param planner 速度规划器指针
|
||||
* @return 1: 需要紧急停止, 0: 正常
|
||||
*/
|
||||
int8_t SpeedPlanner_IsEmergencyStop(SpeedPlanner_t *planner);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SPEED_PLANNER_H
|
||||
@ -48,13 +48,11 @@ inline float Sign(float in) { return (in > 0) ? 1.0f : 0.0f; }
|
||||
inline void ResetMoveVector(MoveVector_t *mv) { memset(mv, 0, sizeof(*mv)); }
|
||||
|
||||
/**
|
||||
* \brief 计算循环值的误差,用于没有负数值,并在一定范围内变化的值
|
||||
* 例如编码器:相差1.5PI其实等于相差-0.5PI
|
||||
*
|
||||
* \param sp 被操作的值
|
||||
* \param fb 变化量
|
||||
* \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 被操作的值变化范围,正数时起效
|
||||
*
|
||||
* \return 函数运行结果
|
||||
*/
|
||||
inline float CircleError(float sp, float fb, float range) {
|
||||
@ -71,9 +69,7 @@ inline float CircleError(float sp, float fb, float range) {
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief 循环加法,用于没有负数值,并在一定范围内变化的值
|
||||
* 例如编码器,在0-2PI内变化,1.5PI + 1.5PI = 1PI
|
||||
*
|
||||
* \brief 循环加法,适用于被操作的值在(0,range)范围内循环的情况
|
||||
* \param origin 被操作的值
|
||||
* \param delta 变化量
|
||||
* \param range 被操作的值变化范围,正数时起效
|
||||
|
||||
@ -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
|
||||
@ -82,21 +86,17 @@ float Sign(float in);
|
||||
void ResetMoveVector(MoveVector_t *mv);
|
||||
|
||||
/**
|
||||
* \brief 计算循环值的误差,用于没有负数值,并在一定范围内变化的值
|
||||
* 例如编码器:相差1.5PI其实等于相差-0.5PI
|
||||
*
|
||||
* \param sp 被操作的值
|
||||
* \param fb 变化量
|
||||
* \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 被操作的值变化范围,正数时起效
|
||||
*
|
||||
* \return 函数运行结果
|
||||
*/
|
||||
float CircleError(float sp, float fb, float range);
|
||||
|
||||
/**
|
||||
* \brief 循环加法,用于没有负数值,并在一定范围内变化的值
|
||||
* 例如编码器,在0-2PI内变化,1.5PI + 1.5PI = 1PI
|
||||
*
|
||||
* \brief 循环加法,适用于被操作的值在(0,range)范围内循环的情况
|
||||
* \param origin 被操作的值
|
||||
* \param delta 变化量
|
||||
* \param range 被操作的值变化范围,正数时起效
|
||||
|
||||
@ -127,6 +127,7 @@ int8_t VMC_ForwardSolve(VMC_t *vmc, float phi1, float phi4, float body_pitch, fl
|
||||
// 计算角加速度
|
||||
leg->dd_theta = VMC_ComputeNumericDerivative(leg->d_theta, leg->last_d_theta, vmc->dt);
|
||||
|
||||
VMC_GroundContactDetection(vmc); // 更新地面接触状态
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -175,10 +176,10 @@ float VMC_GroundContactDetection(VMC_t *vmc) {
|
||||
// Fn = F0*cos(theta) + Tp*sin(theta)/L0 + mg
|
||||
leg->Fn = leg->F_virtual * cosf(leg->theta) +
|
||||
leg->T_virtual * sinf(leg->theta) / leg->L0 +
|
||||
vmc->param.wheel_mass * 9.8f; // 添加轮子重力
|
||||
10.0f; // 添加轮子重力,假设轮子质量约1kg
|
||||
|
||||
// 地面接触判断
|
||||
leg->is_ground_contact = (leg->Fn > 10.0f); // 10N阈值
|
||||
leg->is_ground_contact = (leg->Fn > 20.0f); // 10N阈值
|
||||
|
||||
return leg->Fn;
|
||||
}
|
||||
|
||||
139
User/device/ai.c
Normal file
139
User/device/ai.c
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
AI
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "ai.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/time.h"
|
||||
#include "bsp/uart.h"
|
||||
#include "component/ahrs.h"
|
||||
#include "component/crc16.h"
|
||||
#include "component/crc8.h"
|
||||
#include "component/user_math.h"
|
||||
#include "component/filter.h"
|
||||
|
||||
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
#define AI_LEN_RX_BUFF (sizeof(AI_DownPackage_t))
|
||||
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
|
||||
static uint8_t rxbuf[AI_LEN_RX_BUFF];
|
||||
|
||||
static bool inited = false;
|
||||
|
||||
static osThreadId_t thread_alert;
|
||||
|
||||
static uint32_t drop_message = 0;
|
||||
|
||||
/* Private function -------------------------------------------------------- */
|
||||
|
||||
static void Ai_RxCpltCallback(void) {
|
||||
osThreadFlagsSet(thread_alert, SIGNAL_AI_RAW_REDY);
|
||||
}
|
||||
|
||||
static void Ai_IdleLineCallback(void) {
|
||||
osThreadFlagsSet(thread_alert, SIGNAL_AI_RAW_REDY);
|
||||
}
|
||||
|
||||
/* Exported functions ------------------------------------------------------- */
|
||||
int8_t AI_Init(AI_t *ai) {
|
||||
UNUSED(ai);
|
||||
if (inited) return DEVICE_ERR_INITED;
|
||||
thread_alert = osThreadGetId();
|
||||
|
||||
BSP_UART_RegisterCallback(BSP_UART_AI, BSP_UART_RX_CPLT_CB,
|
||||
Ai_RxCpltCallback);
|
||||
BSP_UART_RegisterCallback(BSP_UART_AI, BSP_UART_IDLE_LINE_CB,
|
||||
Ai_IdleLineCallback);
|
||||
|
||||
inited = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t AI_Restart(AI_t *ai) {
|
||||
UNUSED(ai);
|
||||
__HAL_UART_DISABLE(BSP_UART_GetHandle(BSP_UART_AI));
|
||||
__HAL_UART_ENABLE(BSP_UART_GetHandle(BSP_UART_AI));
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
int8_t AI_StartReceiving(AI_t *ai) {
|
||||
UNUSED(ai);
|
||||
// if (HAL_UART_Receive_DMA(BSP_UART_GetHandle(BSP_UART_AI), rxbuf,
|
||||
// AI_LEN_RX_BUFF) == HAL_OK)
|
||||
if (BSP_UART_Receive(BSP_UART_AI, rxbuf,
|
||||
AI_LEN_RX_BUFF, true) == HAL_OK)
|
||||
return DEVICE_OK;
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
|
||||
bool AI_WaitDmaCplt(void) {
|
||||
return (osThreadFlagsWait(SIGNAL_AI_RAW_REDY, osFlagsWaitAll,0) ==
|
||||
SIGNAL_AI_RAW_REDY);
|
||||
}
|
||||
|
||||
int8_t AI_ParseHost(AI_t *ai) {
|
||||
if (!CRC16_Verify((const uint8_t *)&(rxbuf), sizeof(ai->from_host)))
|
||||
goto error;
|
||||
ai->header.online = true;
|
||||
ai->header.last_online_time = BSP_TIME_Get();
|
||||
memcpy(&(ai->from_host), rxbuf, sizeof(ai->from_host));
|
||||
memset(rxbuf, 0, AI_LEN_RX_BUFF);
|
||||
return DEVICE_OK;
|
||||
|
||||
error:
|
||||
drop_message++;
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
|
||||
int8_t AI_PackMCU(AI_t *ai, const AHRS_Quaternion_t *data){
|
||||
if (ai == NULL || data == NULL) return DEVICE_ERR_NULL;
|
||||
ai->to_host.mcu.id = AI_ID_MCU;
|
||||
ai->to_host.mcu.package.quat=*data;
|
||||
ai->to_host.mcu.package.notice = ai->status;
|
||||
ai->to_host.mcu.crc16 = CRC16_Calc((const uint8_t *)&(ai->to_host.mcu), sizeof(AI_UpPackageMCU_t) - 2, CRC16_INIT);
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
int8_t AI_PackRef(AI_t *ai, const AI_UpPackageReferee_t *data) {
|
||||
if (ai == NULL || data == NULL) return DEVICE_ERR_NULL;
|
||||
ai->to_host.ref = *data;
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
int8_t AI_HandleOffline(AI_t *ai) {
|
||||
if (ai == NULL) return DEVICE_ERR_NULL;
|
||||
if (BSP_TIME_Get() - ai->header.last_online_time >
|
||||
100000) {
|
||||
ai->header.online = false;
|
||||
}
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
int8_t AI_StartSend(AI_t *ai, bool ref_online){
|
||||
if (ai == NULL) return DEVICE_ERR_NULL;
|
||||
|
||||
if (ref_online) {
|
||||
// 发送裁判系统数据和MCU数据
|
||||
if (BSP_UART_Transmit(BSP_UART_AI, (uint8_t *)&(ai->to_host),
|
||||
sizeof(ai->to_host.ref) + sizeof(ai->to_host.mcu), true) == HAL_OK)
|
||||
return DEVICE_OK;
|
||||
else
|
||||
return DEVICE_ERR;
|
||||
} else {
|
||||
// 只发送MCU数据
|
||||
if (BSP_UART_Transmit(BSP_UART_AI, (uint8_t *)&(ai->to_host.mcu),
|
||||
sizeof(ai->to_host.mcu), true) == HAL_OK)
|
||||
return DEVICE_OK;
|
||||
else
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
129
User/device/ai.h
Normal file
129
User/device/ai.h
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
AI
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "component/ahrs.h"
|
||||
#include "component/filter.h"
|
||||
#include "component/user_math.h"
|
||||
#include "device/device.h"
|
||||
#include <cmsis_os2.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Exported constants ------------------------------------------------------- */
|
||||
/* Exported macro ----------------------------------------------------------- */
|
||||
#define AI_ID_MCU (0xC4)
|
||||
#define AI_ID_REF (0xA8)
|
||||
#define AI_ID_AI (0xA1)
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
|
||||
typedef enum {
|
||||
AI_ARMOR_HERO = 0, /*英雄机器人*/
|
||||
AI_ARMOR_INFANTRY, /*步兵机器人*/
|
||||
AI_ARMOR_SENTRY, /*哨兵机器人*/
|
||||
AI_ARMOR_ENGINEER, /*工程机器人*/
|
||||
AI_ARMOR_OUTPOST, /*前哨占*/
|
||||
AI_ARMOR_BASE, /*基地*/
|
||||
AI_ARMOR_NORMAL, /*由AI自动选择*/
|
||||
} AI_ArmorsType_t;
|
||||
|
||||
typedef enum {
|
||||
AI_STATUS_OFF = 0, /* 关闭 */
|
||||
AI_STATUS_AUTOAIM, /* 自瞄 */
|
||||
AI_STATUS_AUTOPICK, /* 自动取矿 */
|
||||
AI_STATUS_AUTOPUT, /* 自动兑矿 */
|
||||
AI_STATUS_AUTOHITBUFF, /* 自动打符 */
|
||||
AI_STATUS_AUTONAV,
|
||||
} AI_Status_t;
|
||||
|
||||
typedef enum {
|
||||
AI_NOTICE_NONE = 0,
|
||||
AI_NOTICE_SEARCH,
|
||||
AI_NOTICE_FIRE,
|
||||
}AI_Notice_t;
|
||||
|
||||
/* 电控 -> 视觉 MCU数据结构体*/
|
||||
typedef struct __packed {
|
||||
AHRS_Quaternion_t quat; /* 四元数 */
|
||||
// struct {
|
||||
// AI_ArmorsType_t armor_type;
|
||||
// AI_Status_t status;
|
||||
// }notice; /* 控制命令 */
|
||||
uint8_t notice;
|
||||
} AI_Protucol_UpDataMCU_t;
|
||||
|
||||
/* 电控 -> 视觉 裁判系统数据结构体*/
|
||||
typedef struct __packed {
|
||||
uint16_t team; /* 本身队伍 */
|
||||
uint16_t time; /* 比赛开始时间 */
|
||||
} AI_Protocol_UpDataReferee_t;
|
||||
|
||||
/* 视觉 -> 电控 数据包结构体*/
|
||||
typedef struct __packed {
|
||||
AHRS_Eulr_t eulr; /* 欧拉角 */
|
||||
MoveVector_t move_vec; /* 运动向量 */
|
||||
uint8_t notice; /* 控制命令 */
|
||||
} AI_Protocol_DownData_t;
|
||||
|
||||
/* 电控 -> 视觉 裁判系统数据包 */
|
||||
typedef struct __packed {
|
||||
uint8_t id; /* 包ID */
|
||||
AI_Protocol_UpDataReferee_t package; /* 数据包 */
|
||||
uint16_t crc16; /* CRC16校验 */
|
||||
} AI_UpPackageReferee_t;
|
||||
|
||||
/* 电控 -> 视觉 MUC数据包 */
|
||||
typedef struct __packed {
|
||||
uint8_t id;
|
||||
AI_Protucol_UpDataMCU_t package;
|
||||
uint16_t crc16;
|
||||
} AI_UpPackageMCU_t;
|
||||
|
||||
/* 视觉 -> 电控 数据包 */
|
||||
typedef struct __packed {
|
||||
uint8_t id; /* 包ID */
|
||||
AI_Protocol_DownData_t package; /* 数据包 */
|
||||
uint16_t crc16; /* CRC16校验 */
|
||||
} AI_DownPackage_t;
|
||||
|
||||
typedef struct __packed {
|
||||
DEVICE_Header_t header; /* 设备通用头部 */
|
||||
AI_DownPackage_t from_host;
|
||||
AI_Status_t status;
|
||||
struct {
|
||||
AI_UpPackageReferee_t ref;
|
||||
AI_UpPackageMCU_t mcu;
|
||||
} to_host;
|
||||
} AI_t;
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
|
||||
int8_t AI_Init(AI_t *ai);
|
||||
|
||||
int8_t AI_Restart(AI_t *ai);
|
||||
|
||||
int8_t AI_StartReceiving(AI_t *ai);
|
||||
|
||||
bool AI_WaitDmaCplt(void);
|
||||
|
||||
int8_t AI_ParseHost(AI_t *ai);
|
||||
|
||||
int8_t AI_PackMCU(AI_t *ai, const AHRS_Quaternion_t *quat);
|
||||
|
||||
int8_t AI_PackRef(AI_t *ai, const AI_UpPackageReferee_t *data);
|
||||
|
||||
int8_t AI_HandleOffline(AI_t *ai);
|
||||
|
||||
int8_t AI_StartSend(AI_t *ai, bool ref_online);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
381
User/device/bmi088.c
Normal file
381
User/device/bmi088.c
Normal file
@ -0,0 +1,381 @@
|
||||
/*
|
||||
BMI088 陀螺仪+加速度计传感器。
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "bmi088.h"
|
||||
|
||||
#include <cmsis_os2.h>
|
||||
#include <gpio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/time.h"
|
||||
#include "bsp/gpio.h"
|
||||
#include "bsp/spi.h"
|
||||
#include "component/user_math.h"
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
#define BMI088_REG_ACCL_CHIP_ID (0x00)
|
||||
#define BMI088_REG_ACCL_ERR (0x02)
|
||||
#define BMI088_REG_ACCL_STATUS (0x03)
|
||||
#define BMI088_REG_ACCL_X_LSB (0x12)
|
||||
#define BMI088_REG_ACCL_X_MSB (0x13)
|
||||
#define BMI088_REG_ACCL_Y_LSB (0x14)
|
||||
#define BMI088_REG_ACCL_Y_MSB (0x15)
|
||||
#define BMI088_REG_ACCL_Z_LSB (0x16)
|
||||
#define BMI088_REG_ACCL_Z_MSB (0x17)
|
||||
#define BMI088_REG_ACCL_SENSORTIME_0 (0x18)
|
||||
#define BMI088_REG_ACCL_SENSORTIME_1 (0x19)
|
||||
#define BMI088_REG_ACCL_SENSORTIME_2 (0x1A)
|
||||
#define BMI088_REG_ACCL_INT_STAT_1 (0x1D)
|
||||
#define BMI088_REG_ACCL_TEMP_MSB (0x22)
|
||||
#define BMI088_REG_ACCL_TEMP_LSB (0x23)
|
||||
#define BMI088_REG_ACCL_CONF (0x40)
|
||||
#define BMI088_REG_ACCL_RANGE (0x41)
|
||||
#define BMI088_REG_ACCL_INT1_IO_CONF (0x53)
|
||||
#define BMI088_REG_ACCL_INT2_IO_CONF (0x54)
|
||||
#define BMI088_REG_ACCL_INT1_INT2_MAP_DATA (0x58)
|
||||
#define BMI088_REG_ACCL_SELF_TEST (0x6D)
|
||||
#define BMI088_REG_ACCL_PWR_CONF (0x7C)
|
||||
#define BMI088_REG_ACCL_PWR_CTRL (0x7D)
|
||||
#define BMI088_REG_ACCL_SOFTRESET (0x7E)
|
||||
|
||||
#define BMI088_REG_GYRO_CHIP_ID (0x00)
|
||||
#define BMI088_REG_GYRO_X_LSB (0x02)
|
||||
#define BMI088_REG_GYRO_X_MSB (0x03)
|
||||
#define BMI088_REG_GYRO_Y_LSB (0x04)
|
||||
#define BMI088_REG_GYRO_Y_MSB (0x05)
|
||||
#define BMI088_REG_GYRO_Z_LSB (0x06)
|
||||
#define BMI088_REG_GYRO_Z_MSB (0x07)
|
||||
#define BMI088_REG_GYRO_INT_STAT_1 (0x0A)
|
||||
#define BMI088_REG_GYRO_RANGE (0x0F)
|
||||
#define BMI088_REG_GYRO_BANDWIDTH (0x10)
|
||||
#define BMI088_REG_GYRO_LPM1 (0x11)
|
||||
#define BMI088_REG_GYRO_SOFTRESET (0x14)
|
||||
#define BMI088_REG_GYRO_INT_CTRL (0x15)
|
||||
#define BMI088_REG_GYRO_INT3_INT4_IO_CONF (0x16)
|
||||
#define BMI088_REG_GYRO_INT3_INT4_IO_MAP (0x18)
|
||||
#define BMI088_REG_GYRO_SELF_TEST (0x3C)
|
||||
|
||||
#define BMI088_CHIP_ID_ACCL (0x1E)
|
||||
#define BMI088_CHIP_ID_GYRO (0x0F)
|
||||
|
||||
#define BMI088_LEN_RX_BUFF (19)
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
#define BMI088_ACCL_NSS_SET() \
|
||||
BSP_GPIO_WritePin(BSP_GPIO_ACCL_CS, GPIO_PIN_SET)
|
||||
#define BMI088_ACCL_NSS_RESET() \
|
||||
BSP_GPIO_WritePin(BSP_GPIO_ACCL_CS, GPIO_PIN_RESET)
|
||||
|
||||
#define BMI088_GYRO_NSS_SET() \
|
||||
BSP_GPIO_WritePin(BSP_GPIO_GYRO_CS, GPIO_PIN_SET)
|
||||
#define BMI088_GYRO_NSS_RESET() \
|
||||
BSP_GPIO_WritePin(BSP_GPIO_GYRO_CS, GPIO_PIN_RESET)
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
typedef enum {
|
||||
BMI_ACCL,
|
||||
BMI_GYRO,
|
||||
} BMI_Device_t;
|
||||
|
||||
/* USER STRUCT BEGIN */
|
||||
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
static uint8_t buffer[2];
|
||||
static uint8_t bmi088_rxbuf[BMI088_LEN_RX_BUFF];
|
||||
|
||||
static osThreadId_t thread_alert;
|
||||
static bool inited = false;
|
||||
|
||||
/* Private function -------------------------------------------------------- */
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
|
||||
static void BMI_WriteSingle(BMI_Device_t dv, uint8_t reg, uint8_t data) {
|
||||
buffer[0] = (reg & 0x7f);
|
||||
buffer[1] = data;
|
||||
|
||||
BSP_TIME_Delay(1);
|
||||
switch (dv) {
|
||||
case BMI_ACCL:
|
||||
BMI088_ACCL_NSS_RESET();
|
||||
break;
|
||||
|
||||
case BMI_GYRO:
|
||||
BMI088_GYRO_NSS_RESET();
|
||||
break;
|
||||
}
|
||||
|
||||
BSP_SPI_Transmit(BSP_SPI_BMI088, buffer, 2u, false);
|
||||
|
||||
switch (dv) {
|
||||
case BMI_ACCL:
|
||||
BMI088_ACCL_NSS_SET();
|
||||
break;
|
||||
|
||||
case BMI_GYRO:
|
||||
BMI088_GYRO_NSS_SET();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t BMI_ReadSingle(BMI_Device_t dv, uint8_t reg) {
|
||||
BSP_TIME_Delay(1);
|
||||
switch (dv) {
|
||||
case BMI_ACCL:
|
||||
BMI088_ACCL_NSS_RESET();
|
||||
break;
|
||||
|
||||
case BMI_GYRO:
|
||||
BMI088_GYRO_NSS_RESET();
|
||||
break;
|
||||
}
|
||||
buffer[0] = (uint8_t)(reg | 0x80);
|
||||
BSP_SPI_Transmit(BSP_SPI_BMI088, buffer, 1u, false);
|
||||
BSP_SPI_Receive(BSP_SPI_BMI088, buffer, 2u, false);
|
||||
|
||||
switch (dv) {
|
||||
case BMI_ACCL:
|
||||
BMI088_ACCL_NSS_SET();
|
||||
return buffer[1];
|
||||
|
||||
case BMI_GYRO:
|
||||
BMI088_GYRO_NSS_SET();
|
||||
return buffer[0];
|
||||
}
|
||||
}
|
||||
|
||||
static void BMI_Read(BMI_Device_t dv, uint8_t reg, uint8_t *data, uint8_t len) {
|
||||
if (data == NULL) return;
|
||||
|
||||
switch (dv) {
|
||||
case BMI_ACCL:
|
||||
BMI088_ACCL_NSS_RESET();
|
||||
break;
|
||||
|
||||
case BMI_GYRO:
|
||||
BMI088_GYRO_NSS_RESET();
|
||||
break;
|
||||
}
|
||||
buffer[0] = (uint8_t)(reg | 0x80);
|
||||
BSP_SPI_Transmit(BSP_SPI_BMI088, buffer, 1u, false);
|
||||
BSP_SPI_Receive(BSP_SPI_BMI088, data, len, true);
|
||||
}
|
||||
|
||||
static void BMI088_RxCpltCallback(void) {
|
||||
if (BSP_GPIO_ReadPin(BSP_GPIO_ACCL_CS) == GPIO_PIN_RESET) {
|
||||
BMI088_ACCL_NSS_SET();
|
||||
osThreadFlagsSet(thread_alert, SIGNAL_BMI088_ACCL_RAW_REDY);
|
||||
}
|
||||
if (BSP_GPIO_ReadPin(BSP_GPIO_GYRO_CS) == GPIO_PIN_RESET) {
|
||||
BMI088_GYRO_NSS_SET();
|
||||
osThreadFlagsSet(thread_alert, SIGNAL_BMI088_GYRO_RAW_REDY);
|
||||
}
|
||||
}
|
||||
|
||||
static void BMI088_AcclIntCallback(void) {
|
||||
osThreadFlagsSet(thread_alert, SIGNAL_BMI088_ACCL_NEW_DATA);
|
||||
}
|
||||
|
||||
static void BMI088_GyroIntCallback(void) {
|
||||
osThreadFlagsSet(thread_alert, SIGNAL_BMI088_GYRO_NEW_DATA);
|
||||
}
|
||||
|
||||
/* Exported functions ------------------------------------------------------- */
|
||||
int8_t BMI088_Init(BMI088_t *bmi088, const BMI088_Cali_t *cali) {
|
||||
if (bmi088 == NULL) return DEVICE_ERR_NULL;
|
||||
if (cali == NULL) return DEVICE_ERR_NULL;
|
||||
if (inited) return DEVICE_ERR_INITED;
|
||||
if ((thread_alert = osThreadGetId()) == NULL) return DEVICE_ERR_NULL;
|
||||
|
||||
bmi088->cali = cali;
|
||||
|
||||
BMI_WriteSingle(BMI_ACCL, BMI088_REG_ACCL_SOFTRESET, 0xB6);
|
||||
BMI_WriteSingle(BMI_GYRO, BMI088_REG_GYRO_SOFTRESET, 0xB6);
|
||||
BSP_TIME_Delay(30);
|
||||
|
||||
/* Switch accl to SPI mode. */
|
||||
BMI_ReadSingle(BMI_ACCL, BMI088_CHIP_ID_ACCL);
|
||||
|
||||
if (BMI_ReadSingle(BMI_ACCL, BMI088_REG_ACCL_CHIP_ID) != BMI088_CHIP_ID_ACCL)
|
||||
return DEVICE_ERR_NO_DEV;
|
||||
|
||||
if (BMI_ReadSingle(BMI_GYRO, BMI088_REG_GYRO_CHIP_ID) != BMI088_CHIP_ID_GYRO)
|
||||
return DEVICE_ERR_NO_DEV;
|
||||
|
||||
BSP_GPIO_DisableIRQ(BSP_GPIO_ACCL_INT);
|
||||
BSP_GPIO_DisableIRQ(BSP_GPIO_GYRO_INT);
|
||||
|
||||
BSP_SPI_RegisterCallback(BSP_SPI_BMI088, BSP_SPI_RX_CPLT_CB,
|
||||
BMI088_RxCpltCallback);
|
||||
BSP_GPIO_RegisterCallback(BSP_GPIO_ACCL_INT, BMI088_AcclIntCallback);
|
||||
BSP_GPIO_RegisterCallback(BSP_GPIO_GYRO_INT, BMI088_GyroIntCallback);
|
||||
|
||||
/* Accl init. */
|
||||
/* Filter setting: Normal. */
|
||||
/* ODR: 0xAB: 800Hz. 0xAA: 400Hz. 0xA9: 200Hz. 0xA8: 100Hz. 0xA6: 25Hz. */
|
||||
BMI_WriteSingle(BMI_ACCL, BMI088_REG_ACCL_CONF, 0xAA);
|
||||
|
||||
/* 0x00: +-3G. 0x01: +-6G. 0x02: +-12G. 0x03: +-24G. */
|
||||
BMI_WriteSingle(BMI_ACCL, BMI088_REG_ACCL_RANGE, 0x01);
|
||||
|
||||
/* INT1 as output. Push-pull. Active low. Output. */
|
||||
BMI_WriteSingle(BMI_ACCL, BMI088_REG_ACCL_INT1_IO_CONF, 0x08);
|
||||
|
||||
/* Map data ready interrupt to INT1. */
|
||||
BMI_WriteSingle(BMI_ACCL, BMI088_REG_ACCL_INT1_INT2_MAP_DATA, 0x04);
|
||||
|
||||
/* Turn on accl. Now we can read data. */
|
||||
BMI_WriteSingle(BMI_ACCL, BMI088_REG_ACCL_PWR_CTRL, 0x04);
|
||||
BSP_TIME_Delay(50);
|
||||
|
||||
/* Gyro init. */
|
||||
/* 0x00: +-2000. 0x01: +-1000. 0x02: +-500. 0x03: +-250. 0x04: +-125. */
|
||||
BMI_WriteSingle(BMI_GYRO, BMI088_REG_GYRO_RANGE, 0x01);
|
||||
|
||||
/* Filter bw: 47Hz. */
|
||||
/* ODR: 0x02: 1000Hz. 0x03: 400Hz. 0x06: 200Hz. 0x07: 100Hz. */
|
||||
BMI_WriteSingle(BMI_GYRO, BMI088_REG_GYRO_BANDWIDTH, 0x03);
|
||||
|
||||
/* INT3 and INT4 as output. Push-pull. Active low. */
|
||||
BMI_WriteSingle(BMI_GYRO, BMI088_REG_GYRO_INT3_INT4_IO_CONF, 0x00);
|
||||
|
||||
/* Map data ready interrupt to INT3. */
|
||||
BMI_WriteSingle(BMI_GYRO, BMI088_REG_GYRO_INT3_INT4_IO_MAP, 0x01);
|
||||
|
||||
/* Enable new data interrupt. */
|
||||
BMI_WriteSingle(BMI_GYRO, BMI088_REG_GYRO_INT_CTRL, 0x80);
|
||||
|
||||
BSP_TIME_Delay(10);
|
||||
|
||||
inited = true;
|
||||
|
||||
BSP_GPIO_EnableIRQ(BSP_GPIO_ACCL_INT);
|
||||
BSP_GPIO_EnableIRQ(BSP_GPIO_GYRO_INT);
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
bool BMI088_GyroStable(AHRS_Gyro_t *gyro) {
|
||||
return ((gyro->x < 0.03f) && (gyro->y < 0.03f) && (gyro->z < 0.03f));
|
||||
}
|
||||
|
||||
uint32_t BMI088_WaitNew() {
|
||||
return osThreadFlagsWait(
|
||||
SIGNAL_BMI088_ACCL_NEW_DATA | SIGNAL_BMI088_GYRO_NEW_DATA, osFlagsWaitAll,
|
||||
osWaitForever);
|
||||
}
|
||||
|
||||
int8_t BMI088_AcclStartDmaRecv() {
|
||||
BMI_Read(BMI_ACCL, BMI088_REG_ACCL_X_LSB, bmi088_rxbuf, BMI088_LEN_RX_BUFF);
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
uint32_t BMI088_AcclWaitDmaCplt() {
|
||||
return osThreadFlagsWait(SIGNAL_BMI088_ACCL_RAW_REDY, osFlagsWaitAll,
|
||||
osWaitForever);
|
||||
}
|
||||
|
||||
int8_t BMI088_GyroStartDmaRecv() {
|
||||
BMI_Read(BMI_GYRO, BMI088_REG_GYRO_X_LSB, bmi088_rxbuf + 7, 6u);
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
uint32_t BMI088_GyroWaitDmaCplt() {
|
||||
return osThreadFlagsWait(SIGNAL_BMI088_GYRO_RAW_REDY, osFlagsWaitAll,
|
||||
osWaitForever);
|
||||
}
|
||||
|
||||
int8_t BMI088_ParseAccl(BMI088_t *bmi088) {
|
||||
if (bmi088 == NULL) return DEVICE_ERR_NULL;
|
||||
|
||||
#if 1
|
||||
int16_t raw_x, raw_y, raw_z;
|
||||
memcpy(&raw_x, bmi088_rxbuf + 1, sizeof(raw_x));
|
||||
memcpy(&raw_y, bmi088_rxbuf + 3, sizeof(raw_y));
|
||||
memcpy(&raw_z, bmi088_rxbuf + 5, sizeof(raw_z));
|
||||
|
||||
bmi088->accl.x = (float)raw_x;
|
||||
bmi088->accl.y = (float)raw_y;
|
||||
bmi088->accl.z = (float)raw_z;
|
||||
|
||||
#else
|
||||
const int16_t *praw_x = (int16_t *)(bmi088_rxbuf + 1);
|
||||
const int16_t *praw_y = (int16_t *)(bmi088_rxbuf + 3);
|
||||
const int16_t *praw_z = (int16_t *)(bmi088_rxbuf + 5);
|
||||
|
||||
bmi088->accl.x = (float)*praw_x;
|
||||
bmi088->accl.y = (float)*praw_y;
|
||||
bmi088->accl.z = (float)*praw_z;
|
||||
|
||||
#endif
|
||||
|
||||
/* 3G: 10920. 6G: 5460. 12G: 2730. 24G: 1365. */
|
||||
bmi088->accl.x /= 5460.0f;
|
||||
bmi088->accl.y /= 5460.0f;
|
||||
bmi088->accl.z /= 5460.0f;
|
||||
|
||||
int16_t raw_temp =
|
||||
(uint16_t)((bmi088_rxbuf[17] << 3) | (bmi088_rxbuf[18] >> 5));
|
||||
|
||||
if (raw_temp > 1023) raw_temp -= 2048;
|
||||
|
||||
bmi088->temp = (float)raw_temp * 0.125f + 23.0f;
|
||||
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
int8_t BMI088_ParseGyro(BMI088_t *bmi088) {
|
||||
if (bmi088 == NULL) return DEVICE_ERR_NULL;
|
||||
|
||||
#if 1
|
||||
/* Gyroscope imu_raw -> degrees/sec -> radians/sec */
|
||||
int16_t raw_x, raw_y, raw_z;
|
||||
memcpy(&raw_x, bmi088_rxbuf + 7, sizeof(raw_x));
|
||||
memcpy(&raw_y, bmi088_rxbuf + 9, sizeof(raw_y));
|
||||
memcpy(&raw_z, bmi088_rxbuf + 11, sizeof(raw_z));
|
||||
|
||||
bmi088->gyro.x = (float)raw_x;
|
||||
bmi088->gyro.y = (float)raw_y;
|
||||
bmi088->gyro.z = (float)raw_z;
|
||||
|
||||
#else
|
||||
/* Gyroscope imu_raw -> degrees/sec -> radians/sec */
|
||||
const int16_t *raw_x = (int16_t *)(bmi088_rxbuf + 7);
|
||||
const int16_t *raw_y = (int16_t *)(bmi088_rxbuf + 9);
|
||||
const int16_t *raw_z = (int16_t *)(bmi088_rxbuf + 11);
|
||||
|
||||
bmi088->gyro.x = (float)*raw_x;
|
||||
bmi088->gyro.y = (float)*raw_y;
|
||||
bmi088->gyro.z = (float)*raw_z;
|
||||
#endif
|
||||
|
||||
/* FS125: 262.144. FS250: 131.072. FS500: 65.536. FS1000: 32.768.
|
||||
* FS2000: 16.384.*/
|
||||
bmi088->gyro.x /= 32.768f;
|
||||
bmi088->gyro.y /= 32.768f;
|
||||
bmi088->gyro.z /= 32.768f;
|
||||
|
||||
bmi088->gyro.x *= M_DEG2RAD_MULT;
|
||||
bmi088->gyro.y *= M_DEG2RAD_MULT;
|
||||
bmi088->gyro.z *= M_DEG2RAD_MULT;
|
||||
|
||||
bmi088->gyro.x -= bmi088->cali->gyro_offset.x;
|
||||
bmi088->gyro.y -= bmi088->cali->gyro_offset.y;
|
||||
bmi088->gyro.z -= bmi088->cali->gyro_offset.z;
|
||||
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
float BMI088_GetUpdateFreq(BMI088_t *bmi088) {
|
||||
(void)bmi088;
|
||||
return 400.0f;
|
||||
}
|
||||
81
User/device/bmi088.h
Normal file
81
User/device/bmi088.h
Normal file
@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "component/ahrs.h"
|
||||
#include "device/device.h"
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
/* Exported constants ------------------------------------------------------- */
|
||||
/* Exported macro ----------------------------------------------------------- */
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
typedef struct {
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} gyro_offset; /* 陀螺仪偏置 */
|
||||
} BMI088_Cali_t; /* BMI088校准数据 */
|
||||
|
||||
typedef struct {
|
||||
DEVICE_Header_t header;
|
||||
AHRS_Accl_t accl;
|
||||
AHRS_Gyro_t gyro;
|
||||
|
||||
float temp; /* 温度 */
|
||||
|
||||
const BMI088_Cali_t *cali;
|
||||
} BMI088_t;
|
||||
|
||||
/* USER STRUCT BEGIN */
|
||||
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
int8_t BMI088_Init(BMI088_t *bmi088, const BMI088_Cali_t *cali);
|
||||
int8_t BMI088_Restart(void);
|
||||
|
||||
bool BMI088_GyroStable(AHRS_Gyro_t *gyro);
|
||||
|
||||
/* Sensor use right-handed coordinate system. */
|
||||
/*
|
||||
x < R(logo)
|
||||
y
|
||||
UP is z
|
||||
All implementation should follow this rule.
|
||||
*/
|
||||
uint32_t BMI088_WaitNew();
|
||||
|
||||
/*
|
||||
BMI088的Accl和Gyro共用同一个DMA通道,所以一次只能读一个传感器。
|
||||
即BMI088_AcclStartDmaRecv() 和 BMI088_AcclWaitDmaCplt() 中间不能
|
||||
出现 BMI088_GyroStartDmaRecv()。
|
||||
*/
|
||||
int8_t BMI088_AcclStartDmaRecv();
|
||||
uint32_t BMI088_AcclWaitDmaCplt();
|
||||
int8_t BMI088_GyroStartDmaRecv();
|
||||
uint32_t BMI088_GyroWaitDmaCplt();
|
||||
int8_t BMI088_ParseAccl(BMI088_t *bmi088);
|
||||
int8_t BMI088_ParseGyro(BMI088_t *bmi088);
|
||||
float BMI088_GetUpdateFreq(BMI088_t *bmi088);
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,51 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "device.h"
|
||||
#include "bsp/pwm.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Exported constants ------------------------------------------------------- */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
typedef struct {
|
||||
DEVICE_Header_t header;
|
||||
BSP_PWM_Channel_t channel;
|
||||
} BUZZER_t;
|
||||
|
||||
/* USER STRUCT BEGIN */
|
||||
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
|
||||
int8_t BUZZER_Init(BUZZER_t *buzzer, BSP_PWM_Channel_t channel);
|
||||
|
||||
|
||||
int8_t BUZZER_Start(BUZZER_t *buzzer);
|
||||
|
||||
|
||||
int8_t BUZZER_Stop(BUZZER_t *buzzer);
|
||||
|
||||
|
||||
int8_t BUZZER_Set(BUZZER_t *buzzer, float freq, float duty_cycle);
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "device.h"
|
||||
#include "bsp/pwm.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Exported constants ------------------------------------------------------- */
|
||||
|
||||
/* USER DEFINE BEGIN */
|
||||
|
||||
/* USER DEFINE END */
|
||||
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
typedef struct {
|
||||
DEVICE_Header_t header;
|
||||
BSP_PWM_Channel_t channel;
|
||||
} BUZZER_t;
|
||||
|
||||
/* USER STRUCT BEGIN */
|
||||
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
|
||||
int8_t BUZZER_Init(BUZZER_t *buzzer, BSP_PWM_Channel_t channel);
|
||||
|
||||
|
||||
int8_t BUZZER_Start(BUZZER_t *buzzer);
|
||||
|
||||
|
||||
int8_t BUZZER_Stop(BUZZER_t *buzzer);
|
||||
|
||||
|
||||
int8_t BUZZER_Set(BUZZER_t *buzzer, float freq, float duty_cycle);
|
||||
|
||||
/* USER FUNCTION BEGIN */
|
||||
|
||||
/* USER FUNCTION END */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -23,10 +23,14 @@ extern "C" {
|
||||
|
||||
/* AUTO GENERATED SIGNALS BEGIN */
|
||||
#define SIGNAL_DR16_RAW_REDY (1u << 0)
|
||||
#define SIGNAL_BMI088_ACCL_RAW_REDY (1u << 1)
|
||||
#define SIGNAL_BMI088_GYRO_RAW_REDY (1u << 2)
|
||||
#define SIGNAL_BMI088_ACCL_NEW_DATA (1u << 3)
|
||||
#define SIGNAL_BMI088_GYRO_NEW_DATA (1u << 4)
|
||||
/* AUTO GENERATED SIGNALS END */
|
||||
|
||||
/* USER SIGNALS BEGIN */
|
||||
|
||||
#define SIGNAL_AI_RAW_REDY (1u << 5)
|
||||
/* USER SIGNALS END */
|
||||
/*设备层通用Header*/
|
||||
typedef struct {
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
bmi088:
|
||||
bsp_config:
|
||||
BSP_GPIO_ACCL_CS: BSP_GPIO_ACCL_CS
|
||||
BSP_GPIO_ACCL_INT: BSP_GPIO_ACCL_INT
|
||||
BSP_GPIO_GYRO_CS: BSP_GPIO_GYRO_CS
|
||||
BSP_GPIO_GYRO_INT: BSP_GPIO_GYRO_INT
|
||||
BSP_SPI_BMI088: BSP_SPI_BMI088
|
||||
enabled: true
|
||||
buzzer:
|
||||
bsp_config:
|
||||
BSP_PWM_BUZZER: BSP_PWM_TIM8_CH1
|
||||
@ -12,6 +20,9 @@ dr16:
|
||||
motor:
|
||||
bsp_config: {}
|
||||
enabled: true
|
||||
motor_dm:
|
||||
bsp_config: {}
|
||||
enabled: true
|
||||
motor_lk:
|
||||
bsp_config: {}
|
||||
enabled: true
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -141,13 +141,13 @@ int8_t DR16_ParseData(DR16_t *dr16){
|
||||
uint16_t key_value = dr16->raw_data.key;
|
||||
|
||||
// 解析键盘位映射(W-B键,位0-15)
|
||||
for (int i = CMD_KEY_W; i <= CMD_KEY_B; i++) {
|
||||
for (int i = DR16_KEY_W; i <= DR16_KEY_B; i++) {
|
||||
dr16->data.keyboard.key[i] = (key_value & (1 << i)) != 0;
|
||||
}
|
||||
|
||||
// 解析鼠标点击
|
||||
dr16->data.keyboard.key[CMD_L_CLICK] = dr16->data.mouse.l_click;
|
||||
dr16->data.keyboard.key[CMD_R_CLICK] = dr16->data.mouse.r_click;
|
||||
dr16->data.keyboard.key[DR16_L_CLICK] = dr16->data.mouse.l_click;
|
||||
dr16->data.keyboard.key[DR16_R_CLICK] = dr16->data.mouse.r_click;
|
||||
|
||||
// 解析第五通道
|
||||
dr16->data.ch_res = 2.0f * ((float)dr16->raw_data.res - DR16_CH_VALUE_MID) / full_range;
|
||||
|
||||
@ -38,33 +38,33 @@ typedef struct __packed {
|
||||
} DR16_RawData_t;
|
||||
|
||||
typedef enum {
|
||||
CMD_SW_ERR = 0,
|
||||
CMD_SW_UP = 1,
|
||||
CMD_SW_MID = 3,
|
||||
CMD_SW_DOWN = 2,
|
||||
DR16_SW_ERR = 0,
|
||||
DR16_SW_UP = 1,
|
||||
DR16_SW_MID = 3,
|
||||
DR16_SW_DOWN = 2,
|
||||
} DR16_SwitchPos_t;
|
||||
|
||||
/* 键盘按键值 */
|
||||
typedef enum {
|
||||
CMD_KEY_W = 0,
|
||||
CMD_KEY_S,
|
||||
CMD_KEY_A,
|
||||
CMD_KEY_D,
|
||||
CMD_KEY_SHIFT,
|
||||
CMD_KEY_CTRL,
|
||||
CMD_KEY_Q,
|
||||
CMD_KEY_E,
|
||||
CMD_KEY_R,
|
||||
CMD_KEY_F,
|
||||
CMD_KEY_G,
|
||||
CMD_KEY_Z,
|
||||
CMD_KEY_X,
|
||||
CMD_KEY_C,
|
||||
CMD_KEY_V,
|
||||
CMD_KEY_B,
|
||||
CMD_L_CLICK,
|
||||
CMD_R_CLICK,
|
||||
CMD_KEY_NUM,
|
||||
DR16_KEY_W = 0,
|
||||
DR16_KEY_S,
|
||||
DR16_KEY_A,
|
||||
DR16_KEY_D,
|
||||
DR16_KEY_SHIFT,
|
||||
DR16_KEY_CTRL,
|
||||
DR16_KEY_Q,
|
||||
DR16_KEY_E,
|
||||
DR16_KEY_R,
|
||||
DR16_KEY_F,
|
||||
DR16_KEY_G,
|
||||
DR16_KEY_Z,
|
||||
DR16_KEY_X,
|
||||
DR16_KEY_C,
|
||||
DR16_KEY_V,
|
||||
DR16_KEY_B,
|
||||
DR16_L_CLICK,
|
||||
DR16_R_CLICK,
|
||||
DR16_KEY_NUM,
|
||||
} DR16_Key_t;
|
||||
|
||||
typedef struct {
|
||||
@ -87,14 +87,13 @@ typedef struct {
|
||||
} mouse; /* 鼠标值 */
|
||||
|
||||
union {
|
||||
bool key[CMD_KEY_NUM]; /* 键盘按键值 */
|
||||
bool key[DR16_KEY_NUM]; /* 键盘按键值 */
|
||||
uint16_t value; /* 键盘按键值的位映射 */
|
||||
} keyboard;
|
||||
|
||||
uint16_t res; /* 保留,未启用 */
|
||||
} DR16_Data_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
DEVICE_Header_t header;
|
||||
DR16_RawData_t raw_data;
|
||||
|
||||
@ -133,7 +133,7 @@ static int8_t MOTOR_DM_SendMITCmd(MOTOR_DM_t *motor, MOTOR_MIT_Output_t *output)
|
||||
frame.dlc = 8;
|
||||
memcpy(frame.data, data, 8);
|
||||
|
||||
BSP_CAN_WaitTxMailboxEmpty(motor->param.can, 1);
|
||||
|
||||
return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR;
|
||||
}
|
||||
|
||||
@ -163,7 +163,6 @@ static int8_t MOTOR_DM_SendPosVelCmd(MOTOR_DM_t *motor, float pos, float vel) {
|
||||
frame.dlc = 8;
|
||||
memcpy(frame.data, data, 8);
|
||||
|
||||
BSP_CAN_WaitTxMailboxEmpty(motor->param.can, 1);
|
||||
return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR;
|
||||
}
|
||||
|
||||
@ -190,7 +189,6 @@ static int8_t MOTOR_DM_SendVelCmd(MOTOR_DM_t *motor, float vel) {
|
||||
frame.dlc = 4;
|
||||
memcpy(frame.data, data, 4);
|
||||
|
||||
BSP_CAN_WaitTxMailboxEmpty(motor->param.can, 1);
|
||||
return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR;
|
||||
}
|
||||
|
||||
@ -460,7 +458,7 @@ int8_t MOTOR_DM_Enable(MOTOR_DM_Param_t *param){
|
||||
frame.data[5] = 0xFF;
|
||||
frame.data[6] = 0xFF;
|
||||
frame.data[7] = 0xFC;
|
||||
BSP_CAN_WaitTxMailboxEmpty(motor->param.can, 1);
|
||||
|
||||
return BSP_CAN_TransmitStdDataFrame(motor->param.can, &frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -378,7 +368,6 @@ int8_t MOTOR_LZ_Enable(MOTOR_LZ_Param_t *param) {
|
||||
if (param == NULL) return DEVICE_ERR_NULL;
|
||||
|
||||
// 构建扩展ID - 使能命令
|
||||
// 格式: 0x300FF7X, 其中X是motor_id的低4位
|
||||
uint32_t ext_id = MOTOR_LZ_BuildExtID(MOTOR_LZ_CMD_ENABLE, param->host_id, param->motor_id);
|
||||
|
||||
// 数据区清零
|
||||
|
||||
@ -111,12 +111,6 @@ typedef struct {
|
||||
*/
|
||||
int8_t MOTOR_LZ_Init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化灵足电机驱动系统
|
||||
* @return 设备状态码
|
||||
*/
|
||||
int8_t MOTOR_LZ_DeInit(void);
|
||||
|
||||
/**
|
||||
* @brief 注册一个灵足电机
|
||||
* @param param 电机参数
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -138,17 +138,23 @@ static void Motor_RM_Decode(MOTOR_RM_t *motor, BSP_CAN_Message_t *msg) {
|
||||
motor->feedback.rotor_abs_angle = rotor_angle;
|
||||
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;
|
||||
}
|
||||
if (motor->motor.reverse) {
|
||||
motor->feedback.rotor_abs_angle = M_2PI - motor->feedback.rotor_abs_angle;
|
||||
motor->feedback.rotor_speed = -motor->feedback.rotor_speed;
|
||||
motor->feedback.torque_current = -motor->feedback.torque_current;
|
||||
}
|
||||
motor->feedback.temp = msg->data[6];
|
||||
motor->motor.feedback.rotor_abs_angle = motor->feedback.rotor_abs_angle;
|
||||
motor->motor.feedback.rotor_speed = motor->feedback.rotor_speed;
|
||||
motor->motor.feedback.torque_current = motor->feedback.torque_current;
|
||||
motor->motor.feedback.temp = motor->feedback.temp;
|
||||
}
|
||||
|
||||
/* Exported functions ------------------------------------------------------- */
|
||||
|
||||
|
||||
int8_t MOTOR_RM_Register(MOTOR_RM_Param_t *param) {
|
||||
if (param == NULL) return DEVICE_ERR_NULL;
|
||||
if (MOTOR_RM_CreateCANManager(param->can) != DEVICE_OK) return DEVICE_ERR;
|
||||
@ -197,7 +203,7 @@ int8_t MOTOR_RM_Update(MOTOR_RM_Param_t *param) {
|
||||
motor->motor.header.online = true;
|
||||
motor->motor.header.last_online_time = BSP_TIME_Get();
|
||||
Motor_RM_Decode(motor, &rx_msg);
|
||||
|
||||
motor->motor.feedback = motor->feedback;
|
||||
return DEVICE_OK;
|
||||
}
|
||||
}
|
||||
@ -227,6 +233,9 @@ int8_t MOTOR_RM_SetOutput(MOTOR_RM_Param_t *param, float value) {
|
||||
if (manager == NULL) return DEVICE_ERR_NO_DEV;
|
||||
if (value > 1.0f) value = 1.0f;
|
||||
if (value < -1.0f) value = -1.0f;
|
||||
if (param->reverse){
|
||||
value = -value;
|
||||
}
|
||||
MOTOR_RM_t *motor = MOTOR_RM_GetMotor(param);
|
||||
if (motor == NULL) return DEVICE_ERR_NO_DEV;
|
||||
int8_t logical_index = MOTOR_RM_GetLogicalIndex(param->id, param->module);
|
||||
@ -282,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;
|
||||
}
|
||||
|
||||
|
||||
@ -1,312 +0,0 @@
|
||||
/* 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;
|
||||
switch (data_type) {
|
||||
case RC_CAN_DATA_JOYSTICK:
|
||||
frame.id = rc_can->param.joy_id;
|
||||
frame.data[0] = (uint8_t)((int16_t)(rc_can->data.joy.ch_l_x * 32768.0f) & 0xFF);
|
||||
frame.data[1] =
|
||||
(uint8_t)(((int16_t)(rc_can->data.joy.ch_l_x * 32768.0f) >> 8) & 0xFF);
|
||||
frame.data[2] = (uint8_t)((int16_t)(rc_can->data.joy.ch_l_y * 32768.0f) & 0xFF);
|
||||
frame.data[3] =
|
||||
(uint8_t)(((int16_t)(rc_can->data.joy.ch_l_y * 32768.0f) >> 8) & 0xFF);
|
||||
frame.data[4] = (uint8_t)((int16_t)(rc_can->data.joy.ch_r_x * 32768.0f) & 0xFF);
|
||||
frame.data[5] =
|
||||
(uint8_t)(((int16_t)(rc_can->data.joy.ch_r_x * 32768.0f) >> 8) & 0xFF);
|
||||
frame.data[6] = (uint8_t)((int16_t)(rc_can->data.joy.ch_r_y * 32768.0f) & 0xFF);
|
||||
frame.data[7] =
|
||||
(uint8_t)(((int16_t)(rc_can->data.joy.ch_r_y * 32768.0f) >> 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);
|
||||
frame.data[2] = (uint8_t)((int16_t)(rc_can->data.sw.ch_res * 32768.0f) & 0xFF);
|
||||
frame.data[3] =
|
||||
(uint8_t)(((int16_t)(rc_can->data.sw.ch_res * 32768.0f) >> 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;
|
||||
frame.data[0] = (uint8_t)((int16_t)(rc_can->data.mouse.x) & 0xFF);
|
||||
frame.data[1] = (uint8_t)(((int16_t)(rc_can->data.mouse.x) >> 8) & 0xFF);
|
||||
frame.data[2] = (uint8_t)((int16_t)(rc_can->data.mouse.y) & 0xFF);
|
||||
frame.data[3] = (uint8_t)(((int16_t)(rc_can->data.mouse.y) >> 8) & 0xFF);
|
||||
frame.data[4] = (uint8_t)((int16_t)(rc_can->data.mouse.z) & 0xFF);
|
||||
frame.data[5] = (uint8_t)(((int16_t)(rc_can->data.mouse.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;
|
||||
}
|
||||
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 */
|
||||
@ -1,157 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "bsp/can.h"
|
||||
#include "device/device.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* 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
|
||||
389
User/device/virtual_chassis.c
Normal file
389
User/device/virtual_chassis.c
Normal file
@ -0,0 +1,389 @@
|
||||
/*
|
||||
* @file virtual_chassis.c
|
||||
* @brief 虚拟底盘设备驱动实现 - 控制端
|
||||
* @details 作为控制端,发送控制命令到底盘,接收底盘反馈数据
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "device/virtual_chassis.h"
|
||||
#include "bsp/can.h"
|
||||
#include "bsp/time.h"
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
/* Private function prototypes ---------------------------------------------- */
|
||||
/**
|
||||
* @brief 解析关节电机反馈数据 (ID: 124-127)
|
||||
* @param chassis 虚拟底盘结构体
|
||||
* @param id CAN ID
|
||||
* @param data 数据包
|
||||
* @return 解析结果
|
||||
*/
|
||||
static int8_t Virtual_Chassis_DecodeJointMotor(Virtual_Chassis_t *chassis, uint16_t id, uint8_t *data) {
|
||||
if (chassis == NULL || data == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
int motor_idx = id - chassis->param.motors.joint_feedback_base_id;
|
||||
if (motor_idx < 0 || motor_idx >= 4) {
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
|
||||
// 解析关节电机反馈数据:转矩电流(2字节) + 位置(3字节) + 速度(3字节)
|
||||
|
||||
// 1. 解析转矩电流 - 前2字节,16位有符号整数 (精度0.01 Nm)
|
||||
int16_t torque_int;
|
||||
memcpy(&torque_int, &data[0], sizeof(int16_t));
|
||||
chassis->data.joint[motor_idx].torque_current = (float)torque_int / 100.0f;
|
||||
|
||||
// 2. 解析位置 - 第3-5字节,24位有符号整数 (精度0.0001 rad)
|
||||
int32_t angle_int = 0;
|
||||
angle_int |= ((int32_t)data[2]) << 16;
|
||||
angle_int |= ((int32_t)data[3]) << 8;
|
||||
angle_int |= ((int32_t)data[4]);
|
||||
|
||||
// 符号扩展(24位转32位)
|
||||
if (angle_int & 0x800000) {
|
||||
angle_int |= 0xFF000000;
|
||||
}
|
||||
chassis->data.joint[motor_idx].rotor_abs_angle = (float)angle_int / 10000.0f;
|
||||
|
||||
// 3. 解析速度 - 第6-8字节,24位有符号整数 (精度0.001 rad/s)
|
||||
int32_t velocity_int = 0;
|
||||
velocity_int |= ((int32_t)data[5]) << 16;
|
||||
velocity_int |= ((int32_t)data[6]) << 8;
|
||||
velocity_int |= ((int32_t)data[7]);
|
||||
|
||||
// 符号扩展(24位转32位)
|
||||
if (velocity_int & 0x800000) {
|
||||
velocity_int |= 0xFF000000;
|
||||
}
|
||||
chassis->data.joint[motor_idx].rotor_speed = (float)velocity_int / 1000.0f;
|
||||
|
||||
chassis->data.joint[motor_idx].temp = 0.0f;
|
||||
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析轮子电机反馈数据 (ID: 130-131)
|
||||
* @param chassis 虚拟底盘结构体
|
||||
* @param id CAN ID
|
||||
* @param data 数据包
|
||||
* @return 解析结果
|
||||
*/
|
||||
static int8_t Virtual_Chassis_DecodeWheelMotor(Virtual_Chassis_t *chassis, uint16_t id, uint8_t *data) {
|
||||
if (chassis == NULL || data == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
int wheel_idx;
|
||||
if (id == chassis->param.motors.wheel_left_feedback_id) {
|
||||
wheel_idx = 0; // 左轮
|
||||
} else if (id == chassis->param.motors.wheel_right_feedback_id) {
|
||||
wheel_idx = 1; // 右轮
|
||||
} else {
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
|
||||
// 解析轮子电机反馈数据:角度(2字节) + 速度(2字节) + 力矩电流(2字节) + 编码器(2字节)
|
||||
|
||||
// 角度 - 精度0.01度,需转换为弧度
|
||||
int16_t angle_int;
|
||||
angle_int = (data[0] << 8) | data[1];
|
||||
chassis->data.wheel[wheel_idx].rotor_abs_angle = (float)angle_int / 100.0f * M_PI / 180.0f;
|
||||
|
||||
// 速度 - 精度1dps,直接赋值
|
||||
int16_t speed_int;
|
||||
speed_int = (data[2] << 8) | data[3];
|
||||
chassis->data.wheel[wheel_idx].rotor_speed = (float)speed_int;
|
||||
|
||||
// 力矩电流
|
||||
int16_t current_int;
|
||||
current_int = (data[4] << 8) | data[5];
|
||||
chassis->data.wheel[wheel_idx].torque_current = (float)current_int;
|
||||
|
||||
// 编码器值(暂不使用)
|
||||
chassis->data.wheel[wheel_idx].temp = 0.0f;
|
||||
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
/* 数据范围定义 - 与发送端一致 */
|
||||
#define ACCEL_CAN_MAX (58.8f)
|
||||
#define ACCEL_CAN_MIN (-58.8f)
|
||||
#define GYRO_CAN_MAX (34.88f)
|
||||
#define GYRO_CAN_MIN (-34.88f)
|
||||
#define PITCH_CAN_MAX (M_PI_2) /* π/2 弧度 ≈ 90° */
|
||||
#define PITCH_CAN_MIN (-M_PI_2) /* -π/2 弧度 ≈ -90° */
|
||||
#define ROLL_CAN_MAX (M_PI) /* π 弧度 ≈ 180° */
|
||||
#define ROLL_CAN_MIN (-M_PI) /* -π 弧度 ≈ -180° */
|
||||
#define YAW_CAN_MAX (M_PI) /* π 弧度 ≈ 180° */
|
||||
#define YAW_CAN_MIN (-M_PI) /* -π 弧度 ≈ -180° */
|
||||
#define QUATERNION_MIN (-1.0f)
|
||||
#define QUATERNION_MAX (1.0f)
|
||||
|
||||
/* 反向映射宏:将16位整数还原为浮点值 */
|
||||
#define UNMAP_FROM_INT16(int_val, min, max) \
|
||||
(((float)(int_val) + 32768.0f) / 65535.0f * ((max) - (min)) + (min))
|
||||
|
||||
/**
|
||||
* @brief 解析IMU数据
|
||||
* @param chassis 虚拟底盘结构体
|
||||
* @param id CAN ID
|
||||
* @param data 数据包
|
||||
* @return 解析结果
|
||||
*/
|
||||
static int8_t Virtual_Chassis_DecodeIMU(Virtual_Chassis_t *chassis, uint16_t id, uint8_t *data) {
|
||||
if (chassis == NULL || data == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
if (id == chassis->param.imu.accl_id) {
|
||||
// 加速度计数据 - 每轴使用16位编码 (6字节总共)
|
||||
int16_t ax_int, ay_int, az_int;
|
||||
|
||||
memcpy(&ax_int, &data[0], sizeof(int16_t));
|
||||
memcpy(&ay_int, &data[2], sizeof(int16_t));
|
||||
memcpy(&az_int, &data[4], sizeof(int16_t));
|
||||
|
||||
// 反向映射到原始浮点值
|
||||
chassis->data.imu.accl.x = UNMAP_FROM_INT16(ax_int, ACCEL_CAN_MIN, ACCEL_CAN_MAX);
|
||||
chassis->data.imu.accl.y = UNMAP_FROM_INT16(ay_int, ACCEL_CAN_MIN, ACCEL_CAN_MAX);
|
||||
chassis->data.imu.accl.z = UNMAP_FROM_INT16(az_int, ACCEL_CAN_MIN, ACCEL_CAN_MAX);
|
||||
}
|
||||
else if (id == chassis->param.imu.gyro_id) {
|
||||
// 陀螺仪数据 - 每轴使用16位编码 (6字节总共)
|
||||
int16_t gx_int, gy_int, gz_int;
|
||||
|
||||
memcpy(&gx_int, &data[0], sizeof(int16_t));
|
||||
memcpy(&gy_int, &data[2], sizeof(int16_t));
|
||||
memcpy(&gz_int, &data[4], sizeof(int16_t));
|
||||
|
||||
// 反向映射到原始浮点值
|
||||
chassis->data.imu.gyro.x = UNMAP_FROM_INT16(gx_int, GYRO_CAN_MIN, GYRO_CAN_MAX);
|
||||
chassis->data.imu.gyro.y = UNMAP_FROM_INT16(gy_int, GYRO_CAN_MIN, GYRO_CAN_MAX);
|
||||
chassis->data.imu.gyro.z = UNMAP_FROM_INT16(gz_int, GYRO_CAN_MIN, GYRO_CAN_MAX);
|
||||
}
|
||||
else if (id == chassis->param.imu.euler_id) {
|
||||
// 欧拉角数据 - 每角使用16位编码 (6字节总共)
|
||||
int16_t yaw_int, pitch_int, roll_int;
|
||||
|
||||
memcpy(&yaw_int, &data[0], sizeof(int16_t));
|
||||
memcpy(&pitch_int, &data[2], sizeof(int16_t));
|
||||
memcpy(&roll_int, &data[4], sizeof(int16_t));
|
||||
|
||||
// 反向映射到原始浮点值
|
||||
chassis->data.imu.euler.yaw = UNMAP_FROM_INT16(yaw_int, YAW_CAN_MIN, YAW_CAN_MAX);
|
||||
chassis->data.imu.euler.pit = UNMAP_FROM_INT16(pitch_int, PITCH_CAN_MIN, PITCH_CAN_MAX);
|
||||
chassis->data.imu.euler.rol = UNMAP_FROM_INT16(roll_int, ROLL_CAN_MIN, ROLL_CAN_MAX);
|
||||
}
|
||||
else if (id == chassis->param.imu.quat_id) {
|
||||
// 四元数数据 - 每个分量使用16位编码 (8字节总共)
|
||||
int16_t q0_int, q1_int, q2_int, q3_int;
|
||||
|
||||
memcpy(&q0_int, &data[0], sizeof(int16_t));
|
||||
memcpy(&q1_int, &data[2], sizeof(int16_t));
|
||||
memcpy(&q2_int, &data[4], sizeof(int16_t));
|
||||
memcpy(&q3_int, &data[6], sizeof(int16_t));
|
||||
|
||||
// 反向映射到原始浮点值
|
||||
chassis->data.imu.quat.q0 = UNMAP_FROM_INT16(q0_int, QUATERNION_MIN, QUATERNION_MAX);
|
||||
chassis->data.imu.quat.q1 = UNMAP_FROM_INT16(q1_int, QUATERNION_MIN, QUATERNION_MAX);
|
||||
chassis->data.imu.quat.q2 = UNMAP_FROM_INT16(q2_int, QUATERNION_MIN, QUATERNION_MAX);
|
||||
chassis->data.imu.quat.q3 = UNMAP_FROM_INT16(q3_int, QUATERNION_MIN, QUATERNION_MAX);
|
||||
}
|
||||
else {
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
/* Exported functions ------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief 初始化虚拟底盘设备
|
||||
*/
|
||||
int8_t Virtual_Chassis_Init(Virtual_Chassis_t *chassis, Virtual_Chassis_Param_t *param) {
|
||||
if (chassis == NULL || param == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
// 复制参数配置
|
||||
chassis->param = *param;
|
||||
|
||||
// 初始化设备头部
|
||||
chassis->header.online = false;
|
||||
chassis->header.last_online_time = 0;
|
||||
|
||||
// 初始化数据
|
||||
memset(&chassis->data, 0, sizeof(Virtual_Chassis_Feedback_t));
|
||||
memset(&chassis->output, 0, sizeof(Virtual_Chassis_Output_t));
|
||||
|
||||
BSP_CAN_Init();
|
||||
|
||||
// 注册关节电机反馈ID (124-127)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
BSP_CAN_RegisterId(chassis->param.motors.can, chassis->param.motors.joint_feedback_base_id + i, 3);
|
||||
}
|
||||
|
||||
// 注册轮子电机反馈ID (130-131)
|
||||
BSP_CAN_RegisterId(chassis->param.motors.can, chassis->param.motors.wheel_left_feedback_id, 3);
|
||||
BSP_CAN_RegisterId(chassis->param.motors.can, chassis->param.motors.wheel_right_feedback_id, 3);
|
||||
|
||||
// 注册IMU数据ID (769-772 / 0x301-0x304)
|
||||
BSP_CAN_RegisterId(chassis->param.imu.can, chassis->param.imu.accl_id, 3);
|
||||
BSP_CAN_RegisterId(chassis->param.imu.can, chassis->param.imu.gyro_id, 3);
|
||||
BSP_CAN_RegisterId(chassis->param.imu.can, chassis->param.imu.euler_id, 3);
|
||||
BSP_CAN_RegisterId(chassis->param.imu.can, chassis->param.imu.quat_id, 3);
|
||||
|
||||
// 设置设备在线状态
|
||||
chassis->header.online = true;
|
||||
chassis->header.last_online_time = BSP_TIME_Get_us();
|
||||
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 更新虚拟底盘数据(接收底盘反馈数据)
|
||||
*/
|
||||
int8_t Virtual_Chassis_Update(Virtual_Chassis_t *chassis) {
|
||||
if (chassis == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
BSP_CAN_Message_t msg;
|
||||
|
||||
// 接收IMU数据
|
||||
if (BSP_CAN_GetMessage(chassis->param.imu.can, chassis->param.imu.accl_id, &msg, BSP_CAN_TIMEOUT_IMMEDIATE) == BSP_OK) {
|
||||
Virtual_Chassis_DecodeIMU(chassis, chassis->param.imu.accl_id, msg.data);
|
||||
}
|
||||
if (BSP_CAN_GetMessage(chassis->param.imu.can, chassis->param.imu.gyro_id, &msg, BSP_CAN_TIMEOUT_IMMEDIATE) == BSP_OK) {
|
||||
Virtual_Chassis_DecodeIMU(chassis, chassis->param.imu.gyro_id, msg.data);
|
||||
}
|
||||
if (BSP_CAN_GetMessage(chassis->param.imu.can, chassis->param.imu.euler_id, &msg, BSP_CAN_TIMEOUT_IMMEDIATE) == BSP_OK) {
|
||||
Virtual_Chassis_DecodeIMU(chassis, chassis->param.imu.euler_id, msg.data);
|
||||
}
|
||||
if (BSP_CAN_GetMessage(chassis->param.imu.can, chassis->param.imu.quat_id, &msg, BSP_CAN_TIMEOUT_IMMEDIATE) == BSP_OK) {
|
||||
Virtual_Chassis_DecodeIMU(chassis, chassis->param.imu.quat_id, msg.data);
|
||||
}
|
||||
|
||||
// 接收关节电机反馈数据 (124-127)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint16_t joint_id = chassis->param.motors.joint_feedback_base_id + i;
|
||||
if (BSP_CAN_GetMessage(chassis->param.motors.can, joint_id, &msg, BSP_CAN_TIMEOUT_IMMEDIATE) == BSP_OK) {
|
||||
Virtual_Chassis_DecodeJointMotor(chassis, joint_id, msg.data);
|
||||
}
|
||||
}
|
||||
|
||||
// 接收轮子电机反馈数据 (130-131)
|
||||
if (BSP_CAN_GetMessage(chassis->param.motors.can, chassis->param.motors.wheel_left_feedback_id, &msg, BSP_CAN_TIMEOUT_IMMEDIATE) == BSP_OK) {
|
||||
Virtual_Chassis_DecodeWheelMotor(chassis, chassis->param.motors.wheel_left_feedback_id, msg.data);
|
||||
}
|
||||
if (BSP_CAN_GetMessage(chassis->param.motors.can, chassis->param.motors.wheel_right_feedback_id, &msg, BSP_CAN_TIMEOUT_IMMEDIATE) == BSP_OK) {
|
||||
Virtual_Chassis_DecodeWheelMotor(chassis, chassis->param.motors.wheel_right_feedback_id, msg.data);
|
||||
}
|
||||
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 发送电机使能命令
|
||||
*/
|
||||
int8_t Virtual_Chassis_Enable(Virtual_Chassis_t *chassis) {
|
||||
if (chassis == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
BSP_CAN_StdDataFrame_t enable_frame = {
|
||||
.id = chassis->param.motors.enable_id,
|
||||
.dlc = 0,
|
||||
.data = {0}
|
||||
};
|
||||
|
||||
chassis->output.enable_joints = true;
|
||||
|
||||
if (BSP_CAN_TransmitStdDataFrame(chassis->param.motors.can, &enable_frame) == BSP_OK) {
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送关节电机力矩控制命令
|
||||
*/
|
||||
int8_t Virtual_Chassis_SendJointTorque(Virtual_Chassis_t *chassis, float torques[4]) {
|
||||
if (chassis == NULL || torques == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
|
||||
BSP_CAN_StdDataFrame_t torque_frame = {
|
||||
.id = chassis->param.motors.joint_cmd_id,
|
||||
.dlc = 8,
|
||||
.data = {0}
|
||||
};
|
||||
|
||||
// 8字节数据分别是4个电机的力矩 (每个电机2字节,有符号整数,精度0.01 Nm)
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int16_t torque_raw = (int16_t)(torques[i] * 100.0f);
|
||||
memcpy(&torque_frame.data[i * 2], &torque_raw, sizeof(int16_t));
|
||||
chassis->output.joint_torque[i] = torques[i];
|
||||
}
|
||||
|
||||
return BSP_CAN_TransmitStdDataFrame(chassis->param.motors.can, &torque_frame) == BSP_OK ? DEVICE_OK : DEVICE_ERR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送轮子电机控制命令
|
||||
*/
|
||||
int8_t Virtual_Chassis_SendWheelCommand(Virtual_Chassis_t *chassis, float left_out, float right_out) {
|
||||
if (chassis == NULL) {
|
||||
return DEVICE_ERR_NULL;
|
||||
}
|
||||
int16_t torque_control = (int16_t)(left_out * 2048);
|
||||
// 构建CAN帧
|
||||
BSP_CAN_StdDataFrame_t tx_frame;
|
||||
tx_frame.id = chassis->param.motors.wheel_left_id;
|
||||
tx_frame.dlc = 8;
|
||||
|
||||
tx_frame.data[0] = 0xA1;
|
||||
tx_frame.data[1] = 0x00;
|
||||
tx_frame.data[2] = 0x00;
|
||||
tx_frame.data[3] = 0x00;
|
||||
tx_frame.data[4] = (uint8_t)(torque_control & 0xFF);
|
||||
tx_frame.data[5] = (uint8_t)((torque_control >> 8) & 0xFF);
|
||||
tx_frame.data[6] = 0x00;
|
||||
tx_frame.data[7] = 0x00;
|
||||
if (BSP_CAN_TransmitStdDataFrame(chassis->param.motors.can, &tx_frame) != BSP_OK) {
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
torque_control = (int16_t)(right_out * 2048);
|
||||
// 构建CAN帧
|
||||
tx_frame.id = chassis->param.motors.wheel_right_id;
|
||||
tx_frame.dlc = 8;
|
||||
tx_frame.data[0] = 0xA1;
|
||||
tx_frame.data[1] = 0x00;
|
||||
tx_frame.data[2] = 0x00;
|
||||
tx_frame.data[3] = 0x00;
|
||||
tx_frame.data[4] = (uint8_t)(torque_control & 0xFF);
|
||||
tx_frame.data[5] = (uint8_t)((torque_control >> 8) & 0xFF);
|
||||
tx_frame.data[6] = 0x00;
|
||||
tx_frame.data[7] = 0x00;
|
||||
if (BSP_CAN_TransmitStdDataFrame(chassis->param.motors.can, &tx_frame) != BSP_OK) {
|
||||
return DEVICE_ERR;
|
||||
}
|
||||
|
||||
return DEVICE_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Private functions -------------------------------------------------------- */
|
||||
118
User/device/virtual_chassis.h
Normal file
118
User/device/virtual_chassis.h
Normal file
@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "device/device.h"
|
||||
#include "device/motor.h"
|
||||
#include "component/ahrs.h"
|
||||
#include "bsp/can.h"
|
||||
|
||||
/* Exported constants ------------------------------------------------------- */
|
||||
|
||||
|
||||
/* Exported macro ----------------------------------------------------------- */
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
|
||||
/* 虚拟底盘反馈数据结构体 */
|
||||
typedef struct {
|
||||
MOTOR_Feedback_t joint[4]; // 4个关节电机反馈数据
|
||||
MOTOR_Feedback_t wheel[2]; // 2个轮子电机反馈数据
|
||||
struct {
|
||||
DEVICE_Header_t header; // 设备通用头部
|
||||
AHRS_Accl_t accl; // 加速度计数据
|
||||
AHRS_Gyro_t gyro; // 陀螺仪数据
|
||||
AHRS_Eulr_t euler; // 欧拉角数据
|
||||
AHRS_Quaternion_t quat; // 四元数数据
|
||||
} imu;
|
||||
} Virtual_Chassis_Feedback_t;
|
||||
|
||||
/* 虚拟底盘输出控制结构体 */
|
||||
typedef struct {
|
||||
float joint_torque[4]; // 4个关节电机目标力矩
|
||||
uint8_t wheel_left_cmd[8]; // 左轮控制命令数据
|
||||
uint8_t wheel_right_cmd[8]; // 右轮控制命令数据
|
||||
bool enable_joints; // 关节使能标志
|
||||
} Virtual_Chassis_Output_t;
|
||||
|
||||
/* 电机CAN参数结构体 */
|
||||
typedef struct {
|
||||
BSP_CAN_t can; // 电机所在CAN总线
|
||||
uint16_t enable_id; // 电机使能命令CAN ID (121)
|
||||
uint16_t joint_cmd_id; // 关节力矩控制命令CAN ID (122)
|
||||
uint16_t wheel_left_id; // 左轮控制命令CAN ID (128)
|
||||
uint16_t wheel_right_id; // 右轮控制命令CAN ID (129)
|
||||
uint16_t joint_feedback_base_id; // 关节反馈基础ID (124-127)
|
||||
uint16_t wheel_left_feedback_id; // 左轮反馈ID (130)
|
||||
uint16_t wheel_right_feedback_id; // 右轮反馈ID (131)
|
||||
} Virtual_Chassis_MotorParam_t;
|
||||
|
||||
/* IMU参数结构体 */
|
||||
typedef struct {
|
||||
BSP_CAN_t can; // IMU所在CAN总线
|
||||
uint16_t accl_id; // 加速度计数据CAN ID (0x301/769)
|
||||
uint16_t gyro_id; // 陀螺仪数据CAN ID (0x302/770)
|
||||
uint16_t euler_id; // 欧拉角数据CAN ID (0x303/771)
|
||||
uint16_t quat_id; // 四元数数据CAN ID (0x304/772)
|
||||
} Virtual_Chassis_IMUParam_t;
|
||||
|
||||
/* 虚拟底盘参数配置结构体 */
|
||||
typedef struct {
|
||||
Virtual_Chassis_MotorParam_t motors; // 4个电机CAN参数
|
||||
Virtual_Chassis_IMUParam_t imu; // IMU CAN参数
|
||||
} Virtual_Chassis_Param_t;
|
||||
|
||||
/* 虚拟底盘设备结构体 */
|
||||
typedef struct {
|
||||
DEVICE_Header_t header; // 设备通用头部
|
||||
Virtual_Chassis_Param_t param; // 参数配置
|
||||
Virtual_Chassis_Feedback_t data; // 反馈数据
|
||||
Virtual_Chassis_Output_t output; // 控制输出
|
||||
} Virtual_Chassis_t;
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief 初始化虚拟底盘设备
|
||||
* @param chassis 虚拟底盘设备结构体指针
|
||||
* @param param 虚拟底盘参数配置指针
|
||||
* @return DEVICE_OK 成功,其他值失败
|
||||
*/
|
||||
int8_t Virtual_Chassis_Init(Virtual_Chassis_t *chassis, Virtual_Chassis_Param_t *param);
|
||||
|
||||
/**
|
||||
* @brief 更新虚拟底盘数据(包括电机和IMU数据)
|
||||
* @param chassis 虚拟底盘设备结构体指针
|
||||
* @return DEVICE_OK 成功,其他值失败
|
||||
*/
|
||||
int8_t Virtual_Chassis_Update(Virtual_Chassis_t *chassis);
|
||||
|
||||
/**
|
||||
* @brief 发送电机使能命令
|
||||
* @param chassis 虚拟底盘设备结构体指针
|
||||
* @return DEVICE_OK 成功,其他值失败
|
||||
*/
|
||||
int8_t Virtual_Chassis_Enable(Virtual_Chassis_t *chassis);
|
||||
|
||||
/**
|
||||
* @brief 发送关节电机力矩控制命令
|
||||
* @param chassis 虚拟底盘设备结构体指针
|
||||
* @param torques 4个关节电机的目标力矩数组
|
||||
* @return DEVICE_OK 成功,其他值失败
|
||||
*/
|
||||
int8_t Virtual_Chassis_SendJointTorque(Virtual_Chassis_t *chassis, float torques[4]);
|
||||
|
||||
/**
|
||||
* @brief 发送轮子电机控制命令
|
||||
* @param chassis 虚拟底盘设备结构体指针
|
||||
* @param left_cmd 左轮控制命令数据(8字节)
|
||||
* @param right_cmd 右轮控制命令数据(8字节)
|
||||
* @return DEVICE_OK 成功,其他值失败
|
||||
*/
|
||||
int8_t Virtual_Chassis_SendWheelCommand(Virtual_Chassis_t *chassis, float left_out, float right_out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@ -24,6 +24,8 @@ extern "C" {
|
||||
#include "device/motor.h"
|
||||
#include "device/motor_lk.h"
|
||||
#include "device/motor_lz.h"
|
||||
#include "device/motor_dm.h"
|
||||
#include "device/virtual_chassis.h"
|
||||
/* Exported constants ------------------------------------------------------- */
|
||||
#define CHASSIS_OK (0) /* 运行正常 */
|
||||
#define CHASSIS_ERR (-1) /* 运行时发现了其他错误 */
|
||||
@ -37,10 +39,18 @@ extern "C" {
|
||||
typedef enum {
|
||||
CHASSIS_MODE_RELAX, /* 放松模式,电机不输出。一般情况底盘初始化之后的模式 */
|
||||
CHASSIS_MODE_RECOVER, /* 复位模式 */
|
||||
CHASSIS_MODE_WHELL_BALANCE, /* 平衡模式,底盘自我平衡 */
|
||||
CHASSIS_MODE_WHELL_LEG_BALANCE, /* 轮子+腿平衡模式,底盘自我平衡 */
|
||||
CHASSIS_MODE_JUMP, /* 跳跃模式,底盘跳跃 */
|
||||
CHASSIS_MODE_ROTOR, /* 小陀螺模式,底盘高速旋转 */
|
||||
} Chassis_Mode_t;
|
||||
|
||||
typedef enum {
|
||||
CHASSIS_JUMP_STATE_READY, /* 准备跳跃 */
|
||||
CHASSIS_JUMP_STATE_JUMPING, /* 跳跃中 */
|
||||
CHASSIS_JUMP_STATE_LANDING, /* 着陆中 */
|
||||
CHASSIS_JUMP_STATE_END, /* 跳跃结束 */
|
||||
} Chassis_JumpState_t;
|
||||
|
||||
typedef struct {
|
||||
Chassis_Mode_t mode; /* 底盘模式 */
|
||||
MoveVector_t move_vec; /* 底盘运动向量 */
|
||||
@ -56,12 +66,12 @@ typedef struct {
|
||||
typedef struct {
|
||||
MOTOR_Feedback_t joint[4]; /* 四个关节电机反馈 */
|
||||
MOTOR_Feedback_t wheel[2]; /* 两个轮子电机反馈 */
|
||||
MOTOR_Feedback_t yaw; /* 云台Yaw轴电机反馈 */
|
||||
Chassis_IMU_t imu; /* IMU数据 */
|
||||
MOTOR_Feedback_t yaw; /* 云台Yaw轴电机反馈 */
|
||||
}Chassis_Feedback_t;
|
||||
|
||||
typedef struct {
|
||||
MOTOR_LZ_MotionParam_t joint[4]; /* 四个关节电机输出 */
|
||||
float joint[4]; /* 四个关节电机输出 */
|
||||
float wheel[2]; /* 两个轮子电机输出 */
|
||||
}Chassis_Output_t;
|
||||
|
||||
@ -69,17 +79,10 @@ typedef struct {
|
||||
/* 底盘参数的结构体,包含所有初始化用的参数,通常是const,存好几组 */
|
||||
typedef struct {
|
||||
|
||||
MOTOR_LZ_Param_t joint_motors[4]; /* 四个关节电机参数 */
|
||||
MOTOR_LK_Param_t wheel_motors[2]; /* 两个轮子电机参数 */
|
||||
|
||||
Virtual_Chassis_Param_t virtual_chassis_param; // 虚拟底盘参数
|
||||
MOTOR_DM_Param_t yaw_motor; /* 云台Yaw轴电机参数 */
|
||||
VMC_Param_t vmc_param[2]; /* VMC参数 */
|
||||
LQR_GainMatrix_t lqr_gains; /* LQR增益矩阵参数 */
|
||||
|
||||
/* LQR控制器参数 */
|
||||
struct {
|
||||
float max_wheel_torque; /* 轮毂电机最大力矩限制 */
|
||||
float max_joint_torque; /* 关节电机最大力矩限制 */
|
||||
} lqr_param;
|
||||
|
||||
KPID_Params_t yaw; /* yaw轴PID控制参数,用于控制底盘朝向 */
|
||||
KPID_Params_t roll; /* roll轴PID控制参数,用于横滚角补偿 */
|
||||
@ -89,6 +92,10 @@ typedef struct {
|
||||
|
||||
float mech_zero_yaw; /* 机械零点 */
|
||||
|
||||
float theta;
|
||||
float x;
|
||||
float phi;
|
||||
|
||||
/* 低通滤波器截止频率 */
|
||||
struct {
|
||||
float in; /* 输入 */
|
||||
@ -116,9 +123,11 @@ typedef struct {
|
||||
Chassis_Output_t output;
|
||||
|
||||
VMC_t vmc_[2]; /* 两条腿的VMC */
|
||||
LQR_Controller_t lqr[2]; /* 两条腿的LQR控制器 */
|
||||
LQR_t lqr[2]; /* 两条腿的LQR控制器 */
|
||||
|
||||
int8_t state;
|
||||
uint64_t jump_time;
|
||||
|
||||
|
||||
float angle;
|
||||
float height;
|
||||
@ -129,6 +138,8 @@ typedef struct {
|
||||
float velocity_x; /* 机体x速度 */
|
||||
float last_velocity_x; /* 上一次速度用于数值微分 */
|
||||
float target_x; /* 目标x位置 */
|
||||
float target_velocity_x; /* 目标速度 */
|
||||
float last_target_velocity_x; /* 上一次目标速度 */
|
||||
} chassis_state;
|
||||
|
||||
/* yaw控制相关 */
|
||||
@ -136,6 +147,7 @@ typedef struct {
|
||||
float target_yaw; /* 目标yaw角度 */
|
||||
float current_yaw; /* 当前yaw角度 */
|
||||
float yaw_force; /* yaw轴控制力矩 */
|
||||
bool is_reversed; /* 是否使用反转的零点(180度零点),影响前后方向 */
|
||||
} yaw_control;
|
||||
|
||||
float wz_multi; /* 小陀螺模式旋转方向 */
|
||||
@ -156,8 +168,8 @@ typedef struct {
|
||||
|
||||
/* 滤波器 */
|
||||
struct {
|
||||
LowPassFilter2p_t *in; /* 反馈值滤波器 */
|
||||
LowPassFilter2p_t *out; /* 输出值滤波器 */
|
||||
LowPassFilter2p_t joint_out[4]; /* 关节输出滤波器 */
|
||||
LowPassFilter2p_t wheel_out[2]; /* 轮子输出滤波器 */
|
||||
} filter;
|
||||
|
||||
} Chassis_t;
|
||||
@ -212,6 +224,7 @@ int8_t Chassis_Control(Chassis_t *c, const Chassis_CMD_t *c_cmd);
|
||||
int8_t Chassis_LQRControl(Chassis_t *c, const Chassis_CMD_t *c_cmd);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* \brief 底盘输出值
|
||||
*
|
||||
|
||||
0
User/module/cmd.c
Normal file
0
User/module/cmd.c
Normal file
19
User/module/cmd.h
Normal file
19
User/module/cmd.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
控制命令
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -5,7 +5,8 @@
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "module/config.h"
|
||||
#include "bsp/can.h"
|
||||
#include "device/rc_can.h"
|
||||
#include "device/motor_dm.h"
|
||||
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
@ -16,12 +17,153 @@
|
||||
|
||||
// 机器人参数配置
|
||||
Config_RobotParam_t robot_config = {
|
||||
|
||||
.imu_param = {
|
||||
.can = BSP_CAN_2,
|
||||
.can_id = 0x6FF,
|
||||
.device_id = 0x42,
|
||||
.master_id = 0x43,
|
||||
.shoot_param = {
|
||||
.trig_step_angle=M_2PI/8,
|
||||
.shot_delay_time=0.05f,
|
||||
.shot_burst_num=50,
|
||||
.fric_motor_param[0] = {
|
||||
.can = BSP_CAN_2,
|
||||
.id = 0x201,
|
||||
.module = MOTOR_M3508,
|
||||
.reverse = true,
|
||||
.gear=false,
|
||||
},
|
||||
.fric_motor_param[1] = {
|
||||
.can = BSP_CAN_2,
|
||||
.id = 0x202,
|
||||
.module = MOTOR_M3508,
|
||||
.reverse = false,
|
||||
.gear=false,
|
||||
},
|
||||
.trig_motor_param = {
|
||||
.can = BSP_CAN_1,
|
||||
.id = 0x201,
|
||||
.module = MOTOR_M2006,
|
||||
.reverse = true,
|
||||
.gear=true,
|
||||
},
|
||||
.fric_follow = {
|
||||
.k=1.0f,
|
||||
.p=1.8f,
|
||||
.i=0.0f,
|
||||
.d=0.0f,
|
||||
.i_limit=0.0f,
|
||||
.out_limit=0.9f,
|
||||
.d_cutoff_freq=30.0f,
|
||||
.range=-1.0f,
|
||||
},
|
||||
|
||||
.fric_err = {
|
||||
.k=1.0f,
|
||||
.p=4.0f,
|
||||
.i=0.4f,
|
||||
.d=0.04f,
|
||||
.i_limit=0.25f,
|
||||
.out_limit=0.25f,
|
||||
.d_cutoff_freq=40.0f,
|
||||
.range=-1.0f,
|
||||
},
|
||||
.trig_omg = {
|
||||
.k=1.0f,
|
||||
.p=1.5f,
|
||||
.i=0.3f,
|
||||
.d=0.5f,
|
||||
.i_limit=0.2f,
|
||||
.out_limit=0.9f,
|
||||
.d_cutoff_freq=-1.0f,
|
||||
.range=-1.0f,
|
||||
},
|
||||
.trig = {
|
||||
.k=1.0f,
|
||||
.p=1.0f,
|
||||
.i=0.1f,
|
||||
.d=0.05f,
|
||||
.i_limit=0.8f,
|
||||
.out_limit=0.5f,
|
||||
.d_cutoff_freq=-1.0f,
|
||||
.range=M_2PI,
|
||||
},
|
||||
.filter.fric = {
|
||||
.in = 30.0f,
|
||||
.out = 30.0f,
|
||||
},
|
||||
.filter.trig = {
|
||||
.in = 30.0f,
|
||||
.out = 30.0f,
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
|
||||
.gimbal_param = {
|
||||
.pid = {
|
||||
.yaw_omega = {
|
||||
.k = 1.0f,
|
||||
.p = 1.0f,
|
||||
.i = 0.3f,
|
||||
.d = 0.0f,
|
||||
.i_limit = 1.0f,
|
||||
.out_limit = 1.0f,
|
||||
.d_cutoff_freq = -1.0f,
|
||||
.range = -1.0f,
|
||||
},
|
||||
.yaw_angle = {
|
||||
.k = 8.0f,
|
||||
.p = 3.0f,
|
||||
.i = 0.0f,
|
||||
.d = 0.0f,
|
||||
.i_limit = 0.0f,
|
||||
.out_limit = 10.0f,
|
||||
.d_cutoff_freq = -1.0f,
|
||||
.range = M_2PI,
|
||||
},
|
||||
.pit_omega = {
|
||||
.k = 0.25f,
|
||||
.p = 1.0f,
|
||||
.i = 0.0f,
|
||||
.d = 0.0f,
|
||||
.i_limit = 1.0f,
|
||||
.out_limit = 1.0f,
|
||||
.d_cutoff_freq = -1.0f,
|
||||
.range = -1.0f,
|
||||
},
|
||||
.pit_angle = {
|
||||
.k = 5.0f,
|
||||
.p = 5.0f,
|
||||
.i = 2.5f,
|
||||
.d = 0.0f,
|
||||
.i_limit = 0.0f,
|
||||
.out_limit = 10.0f,
|
||||
.d_cutoff_freq = -1.0f,
|
||||
.range = M_2PI,
|
||||
},
|
||||
},
|
||||
.mech_zero = {
|
||||
.yaw = 0.0f,
|
||||
.pit = 1.77f,
|
||||
},
|
||||
.travel = {
|
||||
.yaw = -1.0f,
|
||||
.pit = 0.8f,
|
||||
},
|
||||
.low_pass_cutoff_freq = {
|
||||
.out = -1.0f,
|
||||
.gyro = 1000.0f,
|
||||
},
|
||||
.pit_motor ={
|
||||
.can = BSP_CAN_2,
|
||||
.id = 0x206,
|
||||
.gear = false,
|
||||
.module = MOTOR_GM6020,
|
||||
.reverse = true,
|
||||
},
|
||||
.yaw_motor = {
|
||||
.can = BSP_CAN_1,
|
||||
.can_id = 0x1,
|
||||
.master_id = 0x11,
|
||||
.module = MOTOR_DM_J4310,
|
||||
.reverse = false,
|
||||
}
|
||||
},
|
||||
|
||||
.chassis_param = {
|
||||
@ -29,7 +171,7 @@ Config_RobotParam_t robot_config = {
|
||||
.k=1.0f,
|
||||
.p=1.0f,
|
||||
.i=0.0f,
|
||||
.d=0.0f,
|
||||
.d=0.3f,
|
||||
.i_limit=0.0f,
|
||||
.out_limit=1.0f,
|
||||
.d_cutoff_freq=30.0f,
|
||||
@ -38,28 +180,28 @@ Config_RobotParam_t robot_config = {
|
||||
|
||||
.roll={
|
||||
.k=1.0f,
|
||||
.p=5.0f, /* 横滚角比例系数 */
|
||||
.i=0.0f, /* 横滚角积分系数 */
|
||||
.d=0.2f, /* 横滚角微分系数 */
|
||||
.i_limit=0.0f, /* 积分限幅 */
|
||||
.out_limit=0.05f, /* 输出限幅,腿长差值限制 */
|
||||
.p=0.5f, /* 横滚角比例系数 */
|
||||
.i=0.01f, /* 横滚角积分系数 */
|
||||
.d=0.01f, /* 横滚角微分系数 */
|
||||
.i_limit=0.2f, /* 积分限幅 */
|
||||
.out_limit=1.0f, /* 输出限幅,腿长差值限制 */
|
||||
.d_cutoff_freq=30.0f, /* 微分截止频率 */
|
||||
.range=-1.0f, /* 不使用循环误差处理 */
|
||||
},
|
||||
|
||||
.leg_length={
|
||||
.k = 20.0f,
|
||||
.p = 10.0f,
|
||||
.i = 0.0f,
|
||||
.d = 0.0f,
|
||||
.k = 40.0f,
|
||||
.p = 20.0f,
|
||||
.i = 0.01f,
|
||||
.d = 2.0f,
|
||||
.i_limit = 0.0f,
|
||||
.out_limit = 100.0f,
|
||||
.out_limit = 200.0f,
|
||||
.d_cutoff_freq = -1.0f,
|
||||
.range = -1.0f,
|
||||
},
|
||||
.leg_theta={
|
||||
.k=1.0f,
|
||||
.p=5.0f, /* 摆角比例系数 */
|
||||
.k=5.0f,
|
||||
.p=2.0f, /* 摆角比例系数 */
|
||||
.i=0.0f, /* 摆角积分系数 */
|
||||
.d=0.0f, /* 摆角微分系数 */
|
||||
.i_limit=0.0f, /* 积分限幅 */
|
||||
@ -69,12 +211,12 @@ Config_RobotParam_t robot_config = {
|
||||
},
|
||||
|
||||
.tp={
|
||||
.k=1.0f,
|
||||
.p=2.0f, /* 俯仰角比例系数 */
|
||||
.k=4.0f,
|
||||
.p=3.0f, /* 俯仰角比例系数 */
|
||||
.i=0.0f, /* 俯仰角积分系数 */
|
||||
.d=0.0f, /* 俯仰角微分系数 */
|
||||
.i_limit=0.0f, /* 积分限幅 */
|
||||
.out_limit=2.0f, /* 输出限幅,腿长差值限制 */
|
||||
.out_limit=10.0f, /* 输出限幅,腿长差值限制 */
|
||||
.d_cutoff_freq=30.0f, /* 微分截止频率 */
|
||||
.range=-1.0f, /* 不使用循环误差处理 */
|
||||
},
|
||||
@ -84,55 +226,16 @@ Config_RobotParam_t robot_config = {
|
||||
.in = 30.0f,
|
||||
.out = 30.0f,
|
||||
},
|
||||
.joint_motors = {
|
||||
{ // 左髋关节
|
||||
.can = BSP_CAN_1,
|
||||
.motor_id = 124,
|
||||
.host_id = 0xFF,
|
||||
.module = MOTOR_LZ_RSO3,
|
||||
.reverse = false,
|
||||
.mode = MOTOR_LZ_MODE_MOTION,
|
||||
},
|
||||
{ // 左膝关节
|
||||
.can = BSP_CAN_1,
|
||||
.motor_id = 125,
|
||||
.host_id = 0xFF,
|
||||
.module = MOTOR_LZ_RSO3,
|
||||
.reverse = false,
|
||||
.mode = MOTOR_LZ_MODE_MOTION,
|
||||
},
|
||||
{ // 右膝关节
|
||||
.can = BSP_CAN_1,
|
||||
.motor_id = 126,
|
||||
.host_id = 0xFF,
|
||||
.module = MOTOR_LZ_RSO3,
|
||||
.reverse = true,
|
||||
.mode = MOTOR_LZ_MODE_MOTION,
|
||||
},
|
||||
{ // 右髋关节
|
||||
.can = BSP_CAN_1,
|
||||
.motor_id = 127,
|
||||
.host_id = 0xFF,
|
||||
.module = MOTOR_LZ_RSO3,
|
||||
.reverse = true,
|
||||
.mode = MOTOR_LZ_MODE_MOTION,
|
||||
},
|
||||
|
||||
.yaw_motor = { // 云台Yaw轴电机
|
||||
.can = BSP_CAN_2,
|
||||
.can_id = 0x1,
|
||||
.master_id = 0x11,
|
||||
.module = MOTOR_DM_J4310,
|
||||
.reverse = false,
|
||||
},
|
||||
.wheel_motors = {
|
||||
{ // 左轮电机
|
||||
.can = BSP_CAN_1,
|
||||
.id = 0x141,
|
||||
.module = MOTOR_LK_MF9025,
|
||||
.reverse = false,
|
||||
},
|
||||
{ // 右轮电机
|
||||
.can = BSP_CAN_1,
|
||||
.id = 0x142,
|
||||
.module = MOTOR_LK_MF9025,
|
||||
.reverse = true,
|
||||
},
|
||||
},
|
||||
.mech_zero_yaw = 0.0f,
|
||||
|
||||
.mech_zero_yaw = 4.34085676f, /* 250.5度,机械零点 */
|
||||
|
||||
.vmc_param = {
|
||||
{ // 左腿
|
||||
@ -150,72 +253,47 @@ Config_RobotParam_t robot_config = {
|
||||
.hip_length = 0.0f // 髋宽 (L5) (m)
|
||||
}
|
||||
},
|
||||
.lqr_gains ={
|
||||
// .k11_coeff = { -61.932040681074966f, 70.963671642086396f, -37.730841182566571f, -0.296475458388679f }, // theta
|
||||
// .k12_coeff = { -0.586710094600108f, 0.886736272521581f, -3.626144273475104f, 0.057861910518974f }, // d_theta
|
||||
// .k13_coeff = { -17.297031110481019f, 16.286794934166178f, -5.176451154477850f, -0.867260018374823f }, // x
|
||||
// .k14_coeff = { -14.893387150006664f, 14.357020815277332f, -5.244645181873441f, -0.869862096507486f}, // d_x
|
||||
// .k15_coeff = { -75.327876471378886f, 79.786699741548944f, -31.183500053811208f, 5.450635661115236f}, // phi
|
||||
// .k16_coeff = { -3.572234723237025f, 4.164905011076312f, -1.874828497771750f, 0.477913222933688f}, // d_phi
|
||||
// .k21_coeff = { 9.327090608559319f, 1.362675928111105f, -8.092777598567881f, 4.351387652359104f}, // theta
|
||||
// .k22_coeff = { 3.872517778351810f, -3.344077414726880f, 0.589693555828904f, 0.310036629174646f}, // d_theta
|
||||
// .k23_coeff = { -71.615766251649134f, 74.748309711530837f, -28.370490124006626f, 4.483586941100197f }, // x
|
||||
// .k24_coeff = { -68.751866288568962f, 71.204785013044102f, -26.812636604518396f, 4.238479323700952f }, // d_x
|
||||
// .k25_coeff = { 205.6887659080132f, -195.1219729060621f, 62.890188701113303f, 7.452313695653162f }, // phi
|
||||
// .k26_coeff = { 16.162999842656344f, -15.926932704437410f, 5.474613395300429f, -0.002856083635449f }, // d_phi
|
||||
|
||||
|
||||
// .k11_coeff = {-143.1550,156.1754,-98.5282,-11.3781}, // theta
|
||||
// .k12_coeff = {8.3196,-12.4161,-8.3805,-1.6368}, // d_theta
|
||||
// .k13_coeff = {-69.6189,68.5619,-23.3079,-4.1736}, // x
|
||||
// .k14_coeff = {-58.4944,58.2204,-22.8021,-5.2361}, // d_x
|
||||
// .k15_coeff = {-29.6753,40.9947,-20.1188,2.5142}, // phi
|
||||
// .k16_coeff = {-13.1787,15.8361,-7.4061,1.1193}, // d_phi
|
||||
// .k21_coeff = {-76.5141,124.3258,-73.4908,22.0942}, // theta
|
||||
// .k22_coeff = {-9.1845,12.7284,-5.8169,2.8659}, // d_theta
|
||||
// .k23_coeff = {-157.6244,179.9415,-77.2161,14.9075}, // x
|
||||
// .k24_coeff = {-180.5666,202.7656,-85.2869,16.4847}, // d_x
|
||||
// .k25_coeff = {14.3596,-14.2125,4.1210,24.1729}, // phi
|
||||
// .k26_coeff = {20.5751,-19.8014,6.3128,2.8044}, // d_phi
|
||||
.k11_coeff = { -2.046396270532116e+02f, 2.283572275397276e+02f, -99.051642997946473f, 2.549835715107331f }, // theta
|
||||
.k12_coeff = { 0.689999868157742f, 2.004516582917084f, -5.904779142191341f, 0.331840642644740f }, // d_theta
|
||||
.k13_coeff = { -59.058694050901643f, 59.363082825119605f, -20.603451414220757f, 1.137708603718630f }, // x
|
||||
.k14_coeff = { -64.283929532305166f, 65.138737687498519f, -23.323482861600581f, 1.257945614978053f }, // d_x
|
||||
.k15_coeff = { -15.125353856795936f, 34.329224759247211f, -22.500683150450474f, 5.036897782323629f }, // phi
|
||||
.k16_coeff = { 2.707768649521343f, 0.390393176362524f, -2.018231845635338f, 0.697604163191230f }, // d_phi
|
||||
.k21_coeff = { 4.135329288854244e+02f, -3.173913574866715e+02f, 50.321199991176265f, 10.217217753280829f }, // theta
|
||||
.k22_coeff = { 48.042446261620519f, -45.292268634116219f, 13.273044296221686f, 0.006982339078710f }, // d_theta
|
||||
.k23_coeff = { 14.015246608117772f, 10.813301135732024f, -18.216050987625373f, 6.078912501009629f }, // x
|
||||
.k24_coeff = { 21.698411755946285f, 5.621435092936538f, -18.016608013978576f, 6.611542756343175f }, // d_x
|
||||
.k25_coeff = { 4.798120071828828e+02f, -4.728222224549831e+02f, 1.591181202824602e+02f, -6.421997865768036f }, // phi
|
||||
.k26_coeff = { 59.794709871063546f, -60.418062715734166f, 20.991263455753383f, -1.388418937916963f }, // d_phi
|
||||
|
||||
// .k11_coeff = { -1.996305368054721e+02f, 2.260001266392926e+02f, -1.009632659573521e+02f, 2.651403223110949e+00f }, // theta
|
||||
// .k12_coeff = { 1.961704292162323e+00f, 8.251913348469651e-01f, -6.073749575127879e+00f, 3.535645826465822e-01f }, // d_theta
|
||||
// .k13_coeff = { -8.161081261310467e+01f, 8.274264960693915e+01f, -2.904800049859091e+01f, 1.653742354439720e+00f }, // x
|
||||
// .k14_coeff = { -7.455421501651551e+01f, 7.622235638964226e+01f, -2.773307975245958e+01f, 1.535810571728176e+00f }, // d_x
|
||||
// .k15_coeff = { -9.027722377713712e+00f, 2.891936105315331e+01f, -2.106785573613789e+01f, 4.948383450314285e+00f }, // phi
|
||||
// .k16_coeff = { 3.049839709048039e+00f, 4.627952499276505e-02f, -1.922806522134511e+00f, 6.902507101472589e-01f }, // d_phi
|
||||
// .k21_coeff = { 4.373445515700652e+02f, -3.391497363529634e+02f, 5.569210421652366e+01f, 1.081229141610274e+01f }, // theta
|
||||
// .k22_coeff = { 5.284103137531328e+01f, -5.028796043573002e+01f, 1.502532265509508e+01f, -3.077550945526411e-02f }, // d_theta
|
||||
// .k23_coeff = { 3.111787495894651e+01f, 4.872956811853720e+00f, -2.278008499711769e+01f, 8.426190283284837e+00f }, // x
|
||||
// .k24_coeff = { 3.329235562185018e+01f, -5.071568882184936e-01f, -1.928141689016582e+01f, 7.776043600153916e+00f }, // d_x
|
||||
// .k25_coeff = { 4.604866240014122e+02f, -4.577537299814802e+02f, 1.557742271762380e+02f, -6.272551369660446e+00f }, // phi
|
||||
// .k26_coeff = { 5.608646259671771e+01f, -5.728239809509379e+01f, 2.016452460533993e+01f, -1.320817827839268e+00f }, // d_phi
|
||||
|
||||
.lqr_gains = {
|
||||
.k11_coeff = { -2.213202553185133e+02f, 2.353939463356143e+02f, -1.057072351438971e+02f, -1.581085937677281e+00f }, // theta
|
||||
.k12_coeff = { -9.181864404672975e+00f, 8.531964722737065e+00f, -9.696625432480346e+00f, -2.388898921230960e-01f }, // d_theta
|
||||
.k13_coeff = { -6.328339397527442e+01f, 6.270159865929592e+01f, -2.133356351416681e+01f, -2.795774497769496e-01f }, // x
|
||||
.k14_coeff = { -7.428160824353201e+01f, 7.371925049068537e+01f, -2.613745545093503e+01f, -5.994101373770330e-01f }, // d_x
|
||||
.k15_coeff = { -6.968934105907989e+01f, 9.229969229361623e+01f, -4.424018428098277e+01f, 8.098181536555296e+00f }, // phi
|
||||
.k16_coeff = { -1.527045508038401e+01f, 2.030548630730375e+01f, -1.009526207086012e+01f, 2.014358176738665e+00f }, // d_phi
|
||||
.k21_coeff = { 6.254476937997669e+01f, 9.037146968574660e+00f, -4.492072460618583e+01f, 1.770766202994207e+01f }, // theta
|
||||
.k22_coeff = { 3.165057029795604e-02f, 7.350960766534424e+00f, -6.597366624137901e+00f, 2.798506180182324e+00f }, // d_theta
|
||||
.k23_coeff = { -5.827814614802593e+01f, 7.789995488757775e+01f, -3.841148024725668e+01f, 8.034534049078013e+00f }, // x
|
||||
.k24_coeff = { -8.937952443465080e+01f, 1.128943502182752e+02f, -5.293642666103645e+01f, 1.073722383888271e+01f }, // d_x
|
||||
.k25_coeff = { 2.478483065877546e+02f, -2.463640234149189e+02f, 8.359617215530402e+01f, 8.324247402653134e+00f }, // phi
|
||||
.k26_coeff = { 6.307211927250707e+01f, -6.266313408748906e+01f, 2.129449351279647e+01f, 9.249265186231070e-01f }, // d_phi
|
||||
},
|
||||
.theta = 0.0f,
|
||||
.x = 0.0f,
|
||||
.phi = -0.1f,
|
||||
|
||||
.virtual_chassis_param = {
|
||||
.motors = {
|
||||
.can = BSP_CAN_1,
|
||||
.enable_id = 121,
|
||||
.joint_cmd_id = 122,
|
||||
.joint_feedback_base_id = 124,
|
||||
.wheel_left_id = 0x128,
|
||||
.wheel_right_id = 0x129,
|
||||
.wheel_left_feedback_id = 0x82,
|
||||
.wheel_right_feedback_id = 0x83,
|
||||
},
|
||||
.imu = {
|
||||
.can = BSP_CAN_1,
|
||||
.accl_id = 0x301,
|
||||
.gyro_id = 0x302,
|
||||
.euler_id = 0x303,
|
||||
.quat_id = 0x304,
|
||||
},
|
||||
},
|
||||
.lqr_param.max_joint_torque = 20.0f, // 关节电机最大力矩 20Nm
|
||||
.lqr_param.max_wheel_torque = 4.5f, // 轮毂电机最大力矩 2.5Nm
|
||||
},
|
||||
|
||||
.rc_can_param = {
|
||||
.can = BSP_CAN_2,
|
||||
.mode = RC_CAN_MODE_SLAVE,
|
||||
.joy_id = 0x250,
|
||||
.sw_id = 0x251,
|
||||
.mouse_id = 0x252,
|
||||
.keyboard_id = 0x253,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
/* Private function prototypes ---------------------------------------------- */
|
||||
|
||||
@ -9,16 +9,18 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include "device/dm_imu.h"
|
||||
#include "device/motor_lz.h"
|
||||
#include "device/motor_lk.h"
|
||||
#include "module/balance_chassis.h"
|
||||
#include "device/rc_can.h"
|
||||
#include "module/gimbal.h"
|
||||
#include "module/shoot.h"
|
||||
#include "device/virtual_chassis.h"
|
||||
|
||||
typedef struct {
|
||||
DM_IMU_Param_t imu_param;
|
||||
Shoot_Params_t shoot_param;
|
||||
Chassis_Params_t chassis_param;
|
||||
RC_CAN_Param_t rc_can_param;
|
||||
Gimbal_Params_t gimbal_param;
|
||||
Virtual_Chassis_Param_t virtual_chassis_param; // 虚拟底盘参数
|
||||
} Config_RobotParam_t;
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
|
||||
246
User/module/gimbal.c
Normal file
246
User/module/gimbal.c
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 云台模组
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "gimbal.h"
|
||||
#include "bsp/can.h"
|
||||
#include "bsp/time.h"
|
||||
#include <math.h>
|
||||
#include "component/filter.h"
|
||||
#include "component/pid.h"
|
||||
#include "device/motor_dm.h"
|
||||
#include "device/motor_rm.h"
|
||||
|
||||
/* 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);
|
||||
}
|
||||
180
User/module/gimbal.h
Normal file
180
User/module/gimbal.h
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 云台模组
|
||||
*/
|
||||
|
||||
#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
|
||||
306
User/module/shoot.c
Normal file
306
User/module/shoot.c
Normal file
@ -0,0 +1,306 @@
|
||||
|
||||
#include "shoot.h"
|
||||
#include <string.h>
|
||||
#include "can.h"
|
||||
#include "component/filter.h"
|
||||
#include "component/user_math.h"
|
||||
#include <math.h>
|
||||
#include "bsp/time.h"
|
||||
|
||||
static bool last_firecmd;
|
||||
|
||||
static inline void ScaleSumTo1(float *a, float *b) {
|
||||
float sum = *a + *b;
|
||||
if (sum > 1.0f) {
|
||||
float scale = 1.0f / sum;
|
||||
*a *= scale;
|
||||
*b *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int8_t Shoot_SetMode(Shoot_t *s, Shoot_Mode_t mode)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
s->mode=mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_ResetIntegral(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
PID_ResetIntegral(&s->pid.fric_follow[i]);
|
||||
PID_ResetIntegral(&s->pid.fric_err[i]);
|
||||
}
|
||||
PID_ResetIntegral(&s->pid.trig);
|
||||
PID_ResetIntegral(&s->pid.trig_omg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_ResetCalu(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
PID_Reset(&s->pid.fric_follow[i]);
|
||||
PID_Reset(&s->pid.fric_err[i]);
|
||||
LowPassFilter2p_Reset(&s->filter.fric.in[i], 0.0f);
|
||||
LowPassFilter2p_Reset(&s->filter.fric.out[i], 0.0f);
|
||||
}
|
||||
PID_Reset(&s->pid.trig);
|
||||
PID_Reset(&s->pid.trig_omg);
|
||||
LowPassFilter2p_Reset(&s->filter.trig.in, 0.0f);
|
||||
LowPassFilter2p_Reset(&s->filter.trig.out, 0.0f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_ResetOutput(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
s->output.out_follow[i]=0.0f;
|
||||
s->output.out_err[i]=0.0f;
|
||||
s->output.out_fric[i]=0.0f;
|
||||
s->output.lpfout_fric[i]=0.0f;
|
||||
}
|
||||
s->output.outagl_trig=0.0f;
|
||||
s->output.outomg_trig=0.0f;
|
||||
s->output.outlpf_trig=0.0f;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_CaluTargetRPM(Shoot_t *s, float target_speed)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
s->target_variable.target_rpm=4000.0f/MAX_FRIC_RPM;
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* \brief 根据发射弹丸数量及发射频率计算拨弹电机目标角度
|
||||
*
|
||||
* \param s 包含发射数据的结构体
|
||||
* \param num 需要发射的弹丸数量
|
||||
*/
|
||||
int8_t Shoot_CaluTargetAngle(Shoot_t *s, Shoot_CMD_t *cmd)
|
||||
{
|
||||
if (s == NULL || s->shoot_Anglecalu.num_to_shoot == 0) {
|
||||
return -1;
|
||||
}
|
||||
if(s->now - s->shoot_Anglecalu.time_last_shoot >= s->param->shot_delay_time && cmd->firecmd)
|
||||
{
|
||||
s->shoot_Anglecalu.time_last_shoot=s->now;
|
||||
s->target_variable.target_angle += s->param->trig_step_angle;
|
||||
if(s->target_variable.target_angle>M_PI)s->target_variable.target_angle-=M_2PI;
|
||||
else if((s->target_variable.target_angle<-M_PI))s->target_variable.target_angle+=M_2PI;
|
||||
s->shoot_Anglecalu.num_to_shoot--;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_Init(Shoot_t *s, Shoot_Params_t *param, float target_freq)
|
||||
{
|
||||
if (s == NULL || param == NULL || target_freq <= 0.0f) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
s->param=param;
|
||||
|
||||
BSP_CAN_Init();
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++){
|
||||
MOTOR_RM_Register(¶m->fric_motor_param[i]);
|
||||
PID_Init(&s->pid.fric_follow[i], KPID_MODE_CALC_D, target_freq,¶m->fric_follow);
|
||||
LowPassFilter2p_Init(&s->filter.fric.in[i], target_freq, s->param->filter.fric.in);
|
||||
LowPassFilter2p_Init(&s->filter.fric.out[i], target_freq, s->param->filter.fric.out);
|
||||
}
|
||||
MOTOR_RM_Register(¶m->trig_motor_param);
|
||||
PID_Init(&s->pid.trig, KPID_MODE_CALC_D, target_freq,¶m->trig);
|
||||
PID_Init(&s->pid.trig_omg, KPID_MODE_CALC_D, target_freq,¶m->trig_omg);
|
||||
|
||||
LowPassFilter2p_Init(&s->filter.trig.in, target_freq, s->param->filter.trig.in);
|
||||
LowPassFilter2p_Init(&s->filter.trig.out, target_freq, s->param->filter.trig.out);
|
||||
|
||||
memset(&s->shoot_Anglecalu,0,sizeof(s->shoot_Anglecalu));
|
||||
memset(&s->output,0,sizeof(s->output));
|
||||
s->target_variable.target_rpm=5000.0f;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_UpdateFeedback(Shoot_t *s)
|
||||
{
|
||||
if (s == NULL) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
float rpm_sum=0.0f;
|
||||
for(int i = 0; i < SHOOT_FRIC_NUM; i++) {
|
||||
/* 更新摩擦电机反馈 */
|
||||
MOTOR_RM_Update(&s->param->fric_motor_param[i]);
|
||||
MOTOR_RM_t *motor_fed = MOTOR_RM_GetMotor(&s->param->fric_motor_param[i]);
|
||||
if(motor_fed!=NULL)
|
||||
{
|
||||
s->feedback.fric[i]=motor_fed->motor.feedback;
|
||||
}
|
||||
/* 滤波反馈rpm */
|
||||
s->feedback.fil_fric_rpm[i] = LowPassFilter2p_Apply(&s->filter.fric.in[i], s->feedback.fric[i].rotor_speed);
|
||||
/* 归一化rpm */
|
||||
s->feedback.fric_rpm[i] = s->feedback.fil_fric_rpm[i] / MAX_FRIC_RPM;
|
||||
if(s->feedback.fric_rpm[i]>1.0f)s->feedback.fric_rpm[i]=1.0f;
|
||||
if(s->feedback.fric_rpm[i]<-1.0f)s->feedback.fric_rpm[i]=-1.0f;
|
||||
/* 计算平均rpm */
|
||||
rpm_sum+=s->feedback.fric_rpm[i];
|
||||
}
|
||||
s->feedback.fric_avgrpm=rpm_sum/SHOOT_FRIC_NUM;
|
||||
/* 更新拨弹电机反馈 */
|
||||
MOTOR_RM_Update(&s->param->trig_motor_param);
|
||||
MOTOR_RM_t *motor_fed = MOTOR_RM_GetMotor(&s->param->trig_motor_param);
|
||||
if(motor_fed!=NULL)
|
||||
{
|
||||
s->feedback.trig=motor_fed->feedback;
|
||||
}
|
||||
/* 将多圈角度归化到单圈 (-M_PI, M_PI) */
|
||||
s->feedback.trig_angle_cicle = s->feedback.trig.rotor_abs_angle;
|
||||
s->feedback.trig_angle_cicle = fmod(s->feedback.trig_angle_cicle, M_2PI); // 将角度限制在 [-2π, 2π]
|
||||
if (s->feedback.trig_angle_cicle > M_PI) {
|
||||
s->feedback.trig_angle_cicle -= M_2PI; // 调整到 [-π, π]
|
||||
}else if (s->feedback.trig_angle_cicle < -M_PI) {
|
||||
s->feedback.trig_angle_cicle += M_2PI; // 调整到 [-π, π]
|
||||
}
|
||||
|
||||
s->feedback.fil_trig_rpm = LowPassFilter2p_Apply(&s->filter.trig.in, s->feedback.trig.rotor_speed);
|
||||
s->feedback.trig_rpm = s->feedback.trig.rotor_speed / MAX_TRIG_RPM;
|
||||
if(s->feedback.trig_rpm>1.0f)s->feedback.trig_rpm=1.0f; //如果单环效果好就删
|
||||
if(s->feedback.trig_rpm<-1.0f)s->feedback.trig_rpm=-1.0f;
|
||||
|
||||
s->errtosee = s->feedback.fric[0].rotor_speed - s->feedback.fric[1].rotor_speed;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t Shoot_Control(Shoot_t *s, Shoot_CMD_t *cmd)
|
||||
{
|
||||
if (s == NULL || cmd == NULL) {
|
||||
return -1; // 参数错误
|
||||
}
|
||||
s->now = BSP_TIME_Get_us() / 1000000.0f;
|
||||
s->dt = (BSP_TIME_Get_us() - s->lask_wakeup) / 1000000.0f;
|
||||
s->lask_wakeup = BSP_TIME_Get_us();
|
||||
s->online = cmd->online;
|
||||
if(!s->online /*|| s->mode==SHOOT_MODE_SAFE*/){
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
MOTOR_RM_Relax(&s->param->fric_motor_param[i]);
|
||||
}
|
||||
MOTOR_RM_Relax(&s->param->trig_motor_param);
|
||||
}
|
||||
else{
|
||||
switch(s->running_state)
|
||||
{
|
||||
case SHOOT_STATE_IDLE:/*熄火等待*/
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
PID_ResetIntegral(&s->pid.fric_follow[i]);
|
||||
s->output.out_follow[i]=PID_Calc(&s->pid.fric_follow[i],0.0f,s->feedback.fric_rpm[i],0,s->dt);
|
||||
s->output.out_fric[i]=s->output.out_follow[i];
|
||||
s->output.lpfout_fric[i] = LowPassFilter2p_Apply(&s->filter.fric.out[i], s->output.out_fric[i]);
|
||||
MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], s->output.lpfout_fric[i]);
|
||||
}
|
||||
s->output.outagl_trig =PID_Calc(&s->pid.trig,s->target_variable.target_angle,s->feedback.trig_angle_cicle,0,s->dt);
|
||||
s->output.outomg_trig =PID_Calc(&s->pid.trig_omg,s->output.outagl_trig,s->feedback.trig_rpm,0,s->dt);
|
||||
s->output.outlpf_trig =LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
MOTOR_RM_SetOutput(&s->param->trig_motor_param, s->output.outlpf_trig);
|
||||
if(cmd->ready)
|
||||
{
|
||||
Shoot_ResetCalu(s);
|
||||
Shoot_ResetIntegral(s);
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_READY;
|
||||
}
|
||||
break;
|
||||
case SHOOT_STATE_READY:/*准备射击*/
|
||||
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{ /* 计算跟随输出、计算修正输出 */
|
||||
s->output.out_follow[i]=PID_Calc(&s->pid.fric_follow[i],s->target_variable.target_rpm/MAX_FRIC_RPM,s->feedback.fric_rpm[i],0,s->dt);
|
||||
s->output.out_err[i]=PID_Calc(&s->pid.fric_err[i],s->feedback.fric_avgrpm,s->feedback.fric_rpm[i],0,s->dt);
|
||||
/* 按比例缩放并加和输出 */
|
||||
ScaleSumTo1(&s->output.out_follow[i], &s->output.out_err[i]);
|
||||
s->output.out_fric[i]=s->output.out_follow[i]+s->output.out_err[i];
|
||||
/* 滤波 */
|
||||
s->output.lpfout_fric[i] = LowPassFilter2p_Apply(&s->filter.fric.out[i], s->output.out_fric[i]);
|
||||
/* 输出 */
|
||||
MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], s->output.lpfout_fric[i]);
|
||||
}
|
||||
/* 拨弹电机输出 */
|
||||
s->output.outagl_trig =PID_Calc(&s->pid.trig,s->target_variable.target_angle,s->feedback.trig_angle_cicle,0,s->dt);
|
||||
s->output.outomg_trig =PID_Calc(&s->pid.trig_omg,s->output.outagl_trig,s->feedback.trig_rpm,0,s->dt);
|
||||
s->output.outlpf_trig =LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
MOTOR_RM_SetOutput(&s->param->trig_motor_param, s->output.outlpf_trig);
|
||||
|
||||
/* 检查状态机 */
|
||||
if(!cmd->ready)
|
||||
{
|
||||
Shoot_ResetCalu(s);
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_IDLE;
|
||||
}
|
||||
else if(last_firecmd==false&&cmd->firecmd==true)
|
||||
{
|
||||
Shoot_ResetCalu(s);
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_FIRE;
|
||||
s->shoot_Anglecalu.num_to_shoot+=s->param->shot_burst_num;
|
||||
|
||||
}
|
||||
break;
|
||||
case SHOOT_STATE_FIRE:
|
||||
Shoot_CaluTargetAngle(s, cmd);
|
||||
for(int i=0;i<SHOOT_FRIC_NUM;i++)
|
||||
{
|
||||
s->output.out_follow[i]=PID_Calc(&s->pid.fric_follow[i],s->target_variable.target_rpm/MAX_FRIC_RPM,s->feedback.fric_rpm[i],0,s->dt);
|
||||
s->output.out_err[i]=PID_Calc(&s->pid.fric_err[i],s->feedback.fric_avgrpm,s->feedback.fric_rpm[i],0,s->dt);
|
||||
ScaleSumTo1(&s->output.out_follow[i], &s->output.out_err[i]);
|
||||
s->output.out_fric[i]=s->output.out_follow[i]+s->output.out_err[i];
|
||||
s->output.lpfout_fric[i] = LowPassFilter2p_Apply(&s->filter.fric.out[i], s->output.out_fric[i]);
|
||||
MOTOR_RM_SetOutput(&s->param->fric_motor_param[i], s->output.lpfout_fric[i]);
|
||||
}
|
||||
s->output.outagl_trig =PID_Calc(&s->pid.trig,s->target_variable.target_angle,s->feedback.trig_angle_cicle,0,s->dt);
|
||||
s->output.outomg_trig =PID_Calc(&s->pid.trig_omg,s->output.outagl_trig,s->feedback.trig_rpm,0,s->dt);
|
||||
s->output.outlpf_trig =LowPassFilter2p_Apply(&s->filter.trig.out, s->output.outomg_trig);
|
||||
MOTOR_RM_SetOutput(&s->param->trig_motor_param, s->output.outlpf_trig);
|
||||
if(!cmd->firecmd)
|
||||
{
|
||||
Shoot_ResetCalu(s);
|
||||
Shoot_ResetOutput(s);
|
||||
s->running_state=SHOOT_STATE_READY;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s->running_state=SHOOT_STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
MOTOR_RM_Ctrl(&s->param->fric_motor_param[0]);
|
||||
MOTOR_RM_Ctrl(&s->param->trig_motor_param);
|
||||
last_firecmd = cmd->firecmd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
200
User/module/shoot.h
Normal file
200
User/module/shoot.h
Normal file
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* far蛇模组
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "main.h"
|
||||
#include <stdbool.h>
|
||||
#include "component/pid.h"
|
||||
#include "device/motor_rm.h"
|
||||
|
||||
|
||||
/* Exported constants ------------------------------------------------------- */
|
||||
#define SHOOT_OK (0) /* 运行正常 */
|
||||
#define SHOOT_ERR (-1) /* 运行时发现了其他错误 */
|
||||
#define SHOOT_ERR_NULL (-2) /* 运行时发现NULL指针 */
|
||||
#define SHOOT_ERR_MODE (-3) /* 运行时配置了错误的CMD_ChassisMode_t */
|
||||
#define SHOOT_ERR_TYPE (-4) /* 运行时配置了错误的Chassis_Type_t */
|
||||
|
||||
#define SHOOT_FRIC_NUM (2) /* 摩擦轮数量 */
|
||||
#define MAX_FRIC_RPM 7000.0f
|
||||
#define MAX_TRIG_RPM 5000.0f
|
||||
/* Exported macro ----------------------------------------------------------- */
|
||||
/* Exported types ----------------------------------------------------------- */
|
||||
|
||||
typedef enum {
|
||||
SHOOT_STATE_IDLE = 0, // 熄火
|
||||
SHOOT_STATE_READY, // 准备射击
|
||||
SHOOT_STATE_FIRE // 射击
|
||||
} Shoot_State_t;
|
||||
|
||||
typedef enum {
|
||||
SHOOT_MODE_SAFE = 0, // 安全模式
|
||||
SHOOT_MODE_SINGLE, // 单发模式
|
||||
SHOOT_MODE_BURST, // 多发模式
|
||||
SHOOT_MODE_CONTINUE // 连发模式
|
||||
} Shoot_Mode_t;
|
||||
typedef struct {
|
||||
bool online;
|
||||
|
||||
bool ready; /* 准备射击 */
|
||||
bool firecmd; /* 射击指令 */
|
||||
|
||||
} Shoot_CMD_t;
|
||||
typedef struct {
|
||||
MOTOR_Feedback_t fric[SHOOT_FRIC_NUM]; /* 摩擦轮电机反馈 */
|
||||
MOTOR_Feedback_t trig; /* 拨弹电机反馈 */
|
||||
|
||||
float fil_fric_rpm[SHOOT_FRIC_NUM]; /* 滤波后的摩擦轮转速 */
|
||||
float fil_trig_rpm; /* 滤波后的拨弹电机转速*/
|
||||
|
||||
float trig_angle_cicle; /* 拨弹电机减速输出轴单圈角度(0~M_2PI) */
|
||||
|
||||
float fric_rpm[SHOOT_FRIC_NUM]; /* 归一化摩擦轮转速 */
|
||||
float fric_avgrpm; /* 归一化摩擦轮平均转速*/
|
||||
float trig_rpm; /* 归一化拨弹电机转速*/
|
||||
|
||||
}Shoot_Feedback_t;
|
||||
|
||||
typedef struct{
|
||||
float time_last_shoot;
|
||||
uint8_t num_to_shoot;
|
||||
uint8_t num_shooted;
|
||||
}Shoot_AngleCalu_t;
|
||||
|
||||
typedef struct {
|
||||
float out_follow[SHOOT_FRIC_NUM];
|
||||
float out_err[SHOOT_FRIC_NUM];
|
||||
float out_fric[SHOOT_FRIC_NUM];
|
||||
float lpfout_fric[SHOOT_FRIC_NUM];
|
||||
|
||||
|
||||
float outagl_trig;
|
||||
float outomg_trig;
|
||||
float outlpf_trig;
|
||||
}Shoot_Output_t;
|
||||
|
||||
|
||||
/* 底盘参数的结构体,包含所有初始化用的参数,通常是const,存好几组 */
|
||||
typedef struct {
|
||||
float trig_step_angle; /* 每发弹丸拨弹电机转动的角度 */
|
||||
float shot_delay_time; /* 射击间隔时间,单位秒 */
|
||||
uint8_t shot_burst_num; /* 多发模式下一次射击的发数 */
|
||||
|
||||
MOTOR_RM_Param_t fric_motor_param[SHOOT_FRIC_NUM];
|
||||
MOTOR_RM_Param_t trig_motor_param;
|
||||
|
||||
|
||||
KPID_Params_t fric_follow; /* 摩擦轮电机PID控制参数,用于跟随目标速度 */
|
||||
KPID_Params_t fric_err; /* 摩擦轮电机PID控制参数,用于消除转速误差 */
|
||||
KPID_Params_t trig; /* 拨弹电机PID控制参数 */
|
||||
KPID_Params_t trig_omg; /* 拨弹电机PID控制参数 */
|
||||
|
||||
/* 低通滤波器截止频率 */
|
||||
struct {
|
||||
struct{
|
||||
float in; /* 反馈值滤波器 */
|
||||
float out; /* 输出值滤波器 */
|
||||
}fric;
|
||||
struct{
|
||||
float in; /* 反馈值滤波器 */
|
||||
float out; /* 输出值滤波器 */
|
||||
}trig;
|
||||
} filter;
|
||||
} Shoot_Params_t;
|
||||
|
||||
/*
|
||||
* 运行的主结构体,所有这个文件里的函数都在操作这个结构体
|
||||
* 包含了初始化参数,中间变量,输出变量
|
||||
*/
|
||||
typedef struct {
|
||||
bool online;
|
||||
|
||||
float now;
|
||||
uint64_t lask_wakeup;
|
||||
float dt;
|
||||
|
||||
Shoot_Params_t *param; /* */
|
||||
/* 模块通用 */
|
||||
Shoot_State_t running_state; /* 运行状态机 */
|
||||
Shoot_Mode_t mode;
|
||||
/* 反馈信息 */
|
||||
Shoot_Feedback_t feedback;
|
||||
/* 控制信息*/
|
||||
Shoot_AngleCalu_t shoot_Anglecalu;
|
||||
Shoot_Output_t output;
|
||||
/* 目标控制量 */
|
||||
struct {
|
||||
float target_rpm; /* 目标摩擦轮转速 */
|
||||
float target_angle; /* 目标拨弹位置 */
|
||||
}target_variable;
|
||||
|
||||
/* 反馈控制用的PID */
|
||||
struct {
|
||||
KPID_t fric_follow[SHOOT_FRIC_NUM]; /* */
|
||||
KPID_t fric_err[SHOOT_FRIC_NUM]; /* */
|
||||
KPID_t trig;
|
||||
KPID_t trig_omg;
|
||||
} pid;
|
||||
|
||||
/* 滤波器 */
|
||||
struct {
|
||||
struct{
|
||||
LowPassFilter2p_t in[SHOOT_FRIC_NUM]; /* 反馈值滤波器 */
|
||||
LowPassFilter2p_t out[SHOOT_FRIC_NUM]; /* 输出值滤波器 */
|
||||
}fric;
|
||||
struct{
|
||||
LowPassFilter2p_t in; /* 反馈值滤波器 */
|
||||
LowPassFilter2p_t out; /* 输出值滤波器 */
|
||||
}trig;
|
||||
} filter;
|
||||
|
||||
float errtosee; /*调试用*/
|
||||
|
||||
} Shoot_t;
|
||||
|
||||
/* Exported functions prototypes -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* \brief 初始化发射
|
||||
*
|
||||
* \param s 包含发射数据的结构体
|
||||
* \param param 包含发射参数的结构体指针
|
||||
* \param target_freq 任务预期的运行频率
|
||||
*
|
||||
* \return 函数运行结果
|
||||
*/
|
||||
int8_t Shoot_Init(Shoot_t *s, Shoot_Params_t *param, float target_freq);
|
||||
|
||||
/**
|
||||
* \brief 更新反馈
|
||||
*
|
||||
* \param s 包含发射数据的结构体
|
||||
*
|
||||
* \return 函数运行结果
|
||||
*/
|
||||
int8_t Shoot_UpdateFeedback(Shoot_t *s);
|
||||
|
||||
/**
|
||||
* \brief 初始化发射
|
||||
*
|
||||
* \param s 包含发射数据的结构体
|
||||
* \param cmd 包含发射命令的结构体
|
||||
*
|
||||
* \return 函数运行结果
|
||||
*/
|
||||
int8_t Shoot_Control(Shoot_t *s, Shoot_CMD_t *cmd);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
59
User/task/ai.c
Normal file
59
User/task/ai.c
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
ai Task
|
||||
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "cmsis_os2.h"
|
||||
#include "task/user_task.h"
|
||||
/* USER INCLUDE BEGIN */
|
||||
#include "device/ai.h"
|
||||
#include "component/ahrs.h"
|
||||
#include <stdbool.h>
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
/* USER STRUCT BEGIN */
|
||||
AI_t ai;
|
||||
AHRS_Quaternion_t quat;
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private function --------------------------------------------------------- */
|
||||
/* Exported functions ------------------------------------------------------- */
|
||||
void Task_ai(void *argument) {
|
||||
(void)argument; /* 未使用argument,消除警告 */
|
||||
|
||||
|
||||
/* 计算任务运行到指定频率需要等待的tick数 */
|
||||
const uint32_t delay_tick = osKernelGetTickFreq() / AI_FREQ;
|
||||
|
||||
osDelay(AI_INIT_DELAY); /* 延时一段时间再开启任务 */
|
||||
|
||||
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
|
||||
/* USER CODE INIT BEGIN */
|
||||
AI_Init(&ai);
|
||||
/* USER CODE INIT END */
|
||||
|
||||
while (1) {
|
||||
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
||||
/* USER CODE BEGIN */
|
||||
AI_StartReceiving(&ai);
|
||||
if (AI_WaitDmaCplt()) {
|
||||
AI_ParseHost(&ai);
|
||||
} else {
|
||||
AI_HandleOffline(&ai);
|
||||
}
|
||||
if (osMessageQueueGet(task_runtime.msgq.ai.quat, &quat, NULL, 0) == osOK) {
|
||||
AI_PackMCU(&ai, &quat);
|
||||
}
|
||||
|
||||
AI_StartSend(&ai, false);
|
||||
|
||||
/* USER CODE END */
|
||||
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,12 +4,17 @@
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "cmsis_os2.h"
|
||||
#include "task/user_task.h"
|
||||
/* USER INCLUDE BEGIN */
|
||||
#include "bsp/can.h"
|
||||
#include "device/dm_imu.h"
|
||||
#include "module/config.h"
|
||||
#include "module/balance_chassis.h"
|
||||
#include "bsp/mm.h"
|
||||
#include "bsp/pwm.h"
|
||||
#include "bsp/gpio.h"
|
||||
#include "component/ahrs.h"
|
||||
#include "component/pid.h"
|
||||
#include "module/gimbal.h"
|
||||
#include "device/bmi088.h"
|
||||
#include "task/user_task.h"
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
@ -17,8 +22,54 @@
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
/* USER STRUCT BEGIN */
|
||||
DM_IMU_t dm_imu;
|
||||
Chassis_IMU_t chassis_imu_send;
|
||||
BMI088_t bmi088;
|
||||
|
||||
AHRS_t gimbal_ahrs;
|
||||
AHRS_Magn_t magn;
|
||||
AHRS_Eulr_t eulr_to_send;
|
||||
|
||||
KPID_t imu_temp_ctrl_pid;
|
||||
|
||||
Gimbal_IMU_t gimbal_to_send;
|
||||
|
||||
BMI088_Cali_t cali_bmi088= {
|
||||
.gyro_offset = {-0.00149519544f,-0.00268901442f,0.00169837975f},
|
||||
};
|
||||
|
||||
static const KPID_Params_t imu_temp_ctrl_pid_param = {
|
||||
.k = 0.3f,
|
||||
.p = 1.0f,
|
||||
.i = 0.01f,
|
||||
.d = 0.0f,
|
||||
.i_limit = 1.0f,
|
||||
.out_limit = 1.0f,
|
||||
};
|
||||
|
||||
/* 校准相关变量 */
|
||||
typedef enum {
|
||||
CALIB_IDLE, // 空闲状态
|
||||
CALIB_RUNNING, // 正在校准
|
||||
CALIB_DONE // 校准完成
|
||||
} CalibState_t;
|
||||
|
||||
static CalibState_t calib_state = CALIB_IDLE;
|
||||
static uint16_t calib_count = 0;
|
||||
static struct {
|
||||
float x_sum;
|
||||
float y_sum;
|
||||
float z_sum;
|
||||
} gyro_sum= {0.0f,0.0f,0.0f};
|
||||
static void start_gyro_calibration(void) {
|
||||
if (calib_state == CALIB_IDLE) {
|
||||
calib_state = CALIB_RUNNING;
|
||||
calib_count = 0;
|
||||
gyro_sum.x_sum = 0.0f;
|
||||
gyro_sum.y_sum = 0.0f;
|
||||
gyro_sum.z_sum = 0.0f;
|
||||
}
|
||||
}
|
||||
#define CALIB_SAMPLES 5000 // 校准采样数量
|
||||
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private function --------------------------------------------------------- */
|
||||
@ -34,22 +85,80 @@ void Task_atti_esti(void *argument) {
|
||||
|
||||
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
|
||||
/* USER CODE INIT BEGIN */
|
||||
BSP_CAN_Init();
|
||||
// 初始化DM IMU设备
|
||||
DM_IMU_Init(&dm_imu, &Config_GetRobotParam()->imu_param);
|
||||
BMI088_Init(&bmi088,&cali_bmi088);
|
||||
AHRS_Init(&gimbal_ahrs, &magn, BMI088_GetUpdateFreq(&bmi088));
|
||||
PID_Init(&imu_temp_ctrl_pid, KPID_MODE_NO_D,
|
||||
1.0f / BMI088_GetUpdateFreq(&bmi088), &imu_temp_ctrl_pid_param);
|
||||
BSP_PWM_Start(BSP_PWM_IMU_HEAT_PWM);
|
||||
|
||||
/* 注册按钮回调函数并启用中断 */
|
||||
BSP_GPIO_RegisterCallback(BSP_GPIO_USER_KEY, start_gyro_calibration);
|
||||
BSP_GPIO_EnableIRQ(BSP_GPIO_USER_KEY);
|
||||
/* USER CODE INIT END */
|
||||
|
||||
while (1) {
|
||||
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
||||
/* USER CODE BEGIN */
|
||||
|
||||
if (DM_IMU_AutoUpdateAll(&dm_imu) == DEVICE_OK) {
|
||||
osMessageQueueReset(task_runtime.msgq.chassis_imu); // 重置消息队列,防止阻塞
|
||||
chassis_imu_send.accl = dm_imu.data.accl;
|
||||
chassis_imu_send.gyro = dm_imu.data.gyro;
|
||||
chassis_imu_send.euler = dm_imu.data.euler;
|
||||
osMessageQueuePut(task_runtime.msgq.chassis_imu, &chassis_imu_send, 0, 0); // 非阻塞发送IMU数据
|
||||
BMI088_WaitNew();
|
||||
BMI088_AcclStartDmaRecv();
|
||||
BMI088_AcclWaitDmaCplt();
|
||||
|
||||
BMI088_GyroStartDmaRecv();
|
||||
BMI088_GyroWaitDmaCplt();
|
||||
|
||||
/* 锁住RTOS内核防止数据解析过程中断,造成错误 */
|
||||
osKernelLock();
|
||||
/* 接收完所有数据后,把数据从原始字节加工成方便计算的数据 */
|
||||
BMI088_ParseAccl(&bmi088);
|
||||
BMI088_ParseGyro(&bmi088);
|
||||
// IST8310_Parse(&ist8310);
|
||||
|
||||
/* 陀螺仪校准处理 */
|
||||
if (calib_state == CALIB_RUNNING) {
|
||||
/* 累加陀螺仪数据用于计算零偏 */
|
||||
gyro_sum.x_sum += bmi088.gyro.x;
|
||||
gyro_sum.y_sum += bmi088.gyro.y;
|
||||
gyro_sum.z_sum += bmi088.gyro.z;
|
||||
calib_count++;
|
||||
|
||||
/* 达到采样数量后计算平均值并更新零偏 */
|
||||
if (calib_count >= CALIB_SAMPLES) {
|
||||
/* 计算平均值作为零偏 */
|
||||
cali_bmi088.gyro_offset.x = gyro_sum.x_sum / CALIB_SAMPLES;
|
||||
cali_bmi088.gyro_offset.y = gyro_sum.y_sum / CALIB_SAMPLES;
|
||||
cali_bmi088.gyro_offset.z = gyro_sum.z_sum / CALIB_SAMPLES;
|
||||
|
||||
/* 校准完成,重置为空闲状态以便下次校准 */
|
||||
calib_state = CALIB_IDLE;
|
||||
|
||||
/* 重新初始化BMI088以应用新的校准数据 */
|
||||
BMI088_Init(&bmi088, &cali_bmi088);
|
||||
AHRS_Init(&gimbal_ahrs, &magn, BMI088_GetUpdateFreq(&bmi088));
|
||||
}
|
||||
}
|
||||
|
||||
/* 只有在非校准状态下才进行正常的姿态解析 */
|
||||
if (calib_state != CALIB_RUNNING) {
|
||||
/* 根据设备接收到的数据进行姿态解析 */
|
||||
AHRS_Update(&gimbal_ahrs, &bmi088.accl, &bmi088.gyro, &magn);
|
||||
|
||||
/* 根据解析出来的四元数计算欧拉角 */
|
||||
AHRS_GetEulr(&eulr_to_send, &gimbal_ahrs);
|
||||
}
|
||||
|
||||
osKernelUnlock();
|
||||
|
||||
/* 创建修改后的数据副本用于发送到消息队列 */
|
||||
gimbal_to_send.eulr = eulr_to_send;
|
||||
gimbal_to_send.gyro = bmi088.gyro;
|
||||
|
||||
osMessageQueueReset(task_runtime.msgq.gimbal.imu);
|
||||
osMessageQueuePut(task_runtime.msgq.gimbal.imu, &gimbal_to_send, 0, 0);
|
||||
|
||||
osMessageQueuePut(task_runtime.msgq.ai.quat, &gimbal_ahrs.quat, 0, 0);
|
||||
|
||||
BSP_PWM_SetComp(BSP_PWM_IMU_HEAT_PWM, PID_Calc(&imu_temp_ctrl_pid, 40.0f, bmi088.temp, 0.0f, 0.0f));
|
||||
|
||||
/* USER CODE END */
|
||||
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
||||
}
|
||||
|
||||
44
User/task/cmd.c
Normal file
44
User/task/cmd.c
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
cmd Task
|
||||
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "task/user_task.h"
|
||||
/* USER INCLUDE BEGIN */
|
||||
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
/* USER STRUCT BEGIN */
|
||||
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private function --------------------------------------------------------- */
|
||||
/* Exported functions ------------------------------------------------------- */
|
||||
void Task_cmd(void *argument) {
|
||||
(void)argument; /* 未使用argument,消除警告 */
|
||||
|
||||
|
||||
/* 计算任务运行到指定频率需要等待的tick数 */
|
||||
const uint32_t delay_tick = osKernelGetTickFreq() / CMD_FREQ;
|
||||
|
||||
osDelay(CMD_INIT_DELAY); /* 延时一段时间再开启任务 */
|
||||
|
||||
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
|
||||
/* USER CODE INIT BEGIN */
|
||||
|
||||
/* USER CODE INIT END */
|
||||
|
||||
while (1) {
|
||||
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
||||
/* USER CODE BEGIN */
|
||||
|
||||
/* USER CODE END */
|
||||
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
||||
}
|
||||
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
- delay: 0
|
||||
description: ''
|
||||
freq_control: true
|
||||
frequency: 500.0
|
||||
frequency: 120.0
|
||||
function: Task_rc
|
||||
name: rc
|
||||
stack: 256
|
||||
@ -33,3 +33,31 @@
|
||||
function: Task_ctrl_gimbal
|
||||
name: ctrl_gimbal
|
||||
stack: 256
|
||||
- delay: 0
|
||||
description: ''
|
||||
freq_control: true
|
||||
frequency: 500.0
|
||||
function: Task_ctrl_shoot
|
||||
name: ctrl_shoot
|
||||
stack: 256
|
||||
- delay: 0
|
||||
description: ''
|
||||
freq_control: true
|
||||
frequency: 500.0
|
||||
function: Task_monitor
|
||||
name: monitor
|
||||
stack: 256
|
||||
- delay: 0
|
||||
description: ''
|
||||
freq_control: true
|
||||
frequency: 500.0
|
||||
function: Task_ai
|
||||
name: ai
|
||||
stack: 256
|
||||
- delay: 0
|
||||
description: ''
|
||||
freq_control: true
|
||||
frequency: 500.0
|
||||
function: Task_cmd
|
||||
name: cmd
|
||||
stack: 256
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "bsp/can.h"
|
||||
#include "device/motor_lk.h"
|
||||
#include "device/motor_lz.h"
|
||||
#include "device/virtual_chassis.h"
|
||||
#include "module/config.h"
|
||||
#include "module/balance_chassis.h"
|
||||
|
||||
@ -21,7 +22,7 @@
|
||||
/* USER STRUCT BEGIN */
|
||||
Chassis_t chassis;
|
||||
Chassis_CMD_t chassis_cmd = {
|
||||
.mode = CHASSIS_MODE_RECOVER, // 改为RECOVER模式,让电机先启动
|
||||
.mode = CHASSIS_MODE_RELAX, // 改为RECOVER模式,让电机先启动
|
||||
.move_vec = {
|
||||
.vx = 0.0f,
|
||||
.vy = 0.0f,
|
||||
@ -30,7 +31,7 @@ Chassis_CMD_t chassis_cmd = {
|
||||
.height = 0.0f,
|
||||
};
|
||||
Chassis_IMU_t chassis_imu;
|
||||
Chassis_t chassis1;
|
||||
// Virtual_Chassis_t virtual_chassis; // 添加虚拟底盘设备
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private function --------------------------------------------------------- */
|
||||
@ -49,22 +50,19 @@ void Task_ctrl_chassis(void *argument) {
|
||||
|
||||
Chassis_Init(&chassis, &Config_GetRobotParam()->chassis_param, CTRL_CHASSIS_FREQ);
|
||||
|
||||
|
||||
/* USER CODE INIT END */
|
||||
|
||||
while (1) {
|
||||
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
||||
/* USER CODE BEGIN */
|
||||
if (osMessageQueueGet(task_runtime.msgq.chassis_imu, &chassis_imu, NULL, 0) == osOK) {
|
||||
chassis.feedback.imu = chassis_imu;
|
||||
if (osMessageQueueGet(task_runtime.msgq.chassis.cmd, &chassis_cmd, NULL, 0) != osOK) {
|
||||
// 没有新命令,保持上次命令
|
||||
}
|
||||
if (osMessageQueueGet(task_runtime.msgq.Chassis_cmd, &chassis_cmd, NULL, 0) == osOK) {
|
||||
// 成功接收到命令,更新底盘命令
|
||||
}
|
||||
|
||||
osMessageQueueGet(task_runtime.msgq.chassis.yaw, &chassis.feedback.yaw, NULL, 0);
|
||||
|
||||
Chassis_UpdateFeedback(&chassis);
|
||||
Chassis_Control(&chassis, &chassis_cmd);
|
||||
|
||||
Chassis_Control(&chassis, &chassis_cmd);
|
||||
|
||||
/* USER CODE END */
|
||||
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
||||
|
||||
@ -4,16 +4,11 @@
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "device/motor_rm.h"
|
||||
#include "task/user_task.h"
|
||||
/* USER INCLUDE BEGIN */
|
||||
#include "bsp/can.h"
|
||||
#include "device/motor_dm.h"
|
||||
#include "device/motor_rm.h"
|
||||
#include "device/motor.h"
|
||||
#include "component/pid.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "component/ahrs.h"
|
||||
#include "module/gimbal.h"
|
||||
#include "module/config.h"
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
@ -21,45 +16,9 @@
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
/* USER STRUCT BEGIN */
|
||||
MOTOR_DM_Param_t dm_4310_param = {
|
||||
.can = BSP_CAN_1,
|
||||
.master_id = 0x11, // 主站ID
|
||||
.can_id = 0x01, // 反馈ID 10进制是85
|
||||
.module = MOTOR_DM_J4310,
|
||||
.reverse = false,
|
||||
};
|
||||
|
||||
// MOTOR_RM_Param_t m2006 = {
|
||||
// .can = BSP_CAN_1,
|
||||
// .id = 0x201,
|
||||
// .module = MOTOR_M2006,
|
||||
// .reverse = false,
|
||||
// .gear = true,
|
||||
// };
|
||||
|
||||
MOTOR_MIT_Output_t gimbal_output = {
|
||||
.angle = 0.0f,
|
||||
.velocity = 0.0f,
|
||||
.torque = 0.0f,
|
||||
.kp = 0.0f,
|
||||
.kd = 0.0f,
|
||||
};
|
||||
MOTOR_DM_t *dm_4310_motor = NULL;
|
||||
|
||||
// MOTOR_Feedback_t m2006_fb;
|
||||
MOTOR_Feedback_t dm_4310_fb;
|
||||
|
||||
// KPID_Params_t gimbal_angle_pid = {
|
||||
// .leg_theta={
|
||||
// .k=1.0f,
|
||||
// .p=5.0f, /* 摆角比例系数 */
|
||||
// .i=0.0f, /* 摆角积分系数 */
|
||||
// .d=0.0f, /* 摆角微分系数 */
|
||||
// .i_limit=0.0f, /* 积分限幅 */
|
||||
// .out_limit=0.05f, /* 输出限幅,腿长差值限制 */
|
||||
// .d_cutoff_freq=30.0f, /* 微分截止频率 */
|
||||
// .range=-1.0f, /* 不使用循环误差处理 */
|
||||
// };
|
||||
Gimbal_t gimbal;
|
||||
Gimbal_IMU_t gimbal_imu;
|
||||
Gimbal_CMD_t gimbal_cmd;
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private function --------------------------------------------------------- */
|
||||
@ -75,23 +34,26 @@ void Task_ctrl_gimbal(void *argument) {
|
||||
|
||||
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
|
||||
/* USER CODE INIT BEGIN */
|
||||
BSP_CAN_Init();
|
||||
// MOTOR_DM_Register(&dm_4310_param);
|
||||
|
||||
Gimbal_Init(&gimbal, &Config_GetRobotParam()->gimbal_param, CTRL_GIMBAL_FREQ);
|
||||
/* USER CODE INIT END */
|
||||
|
||||
while (1) {
|
||||
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
||||
/* USER CODE BEGIN */
|
||||
// MOTOR_DM_Update(&dm_4310_param);
|
||||
// MOTOR_DM_t *dm_4310_motor = MOTOR_DM_GetMotor(&dm_4310_param);
|
||||
// if (dm_4310_motor) {
|
||||
// dm_4310_fb = dm_4310_motor->motor.feedback;
|
||||
// }
|
||||
// if (dm_4310_motor->motor.header.online == false) {
|
||||
// MOTOR_DM_Enable(&dm_4310_param); // 使能电机
|
||||
// } else {
|
||||
// MOTOR_DM_MITCtrl(&dm_4310_param, &gimbal_output);
|
||||
// }
|
||||
if (osMessageQueueGet(task_runtime.msgq.gimbal.imu, &gimbal_imu, NULL, 0) == osOK) {
|
||||
Gimbal_UpdateIMU(&gimbal, &gimbal_imu);
|
||||
}
|
||||
osMessageQueueGet(task_runtime.msgq.gimbal.cmd, &gimbal_cmd, NULL, 0);
|
||||
|
||||
|
||||
Gimbal_UpdateFeedback(&gimbal);
|
||||
|
||||
// osMessageQueueReset(task_runtime.msgq.chassis_yaw);
|
||||
osMessageQueuePut(task_runtime.msgq.chassis.yaw, &gimbal.feedback.motor.yaw, 0, 0);
|
||||
|
||||
Gimbal_Control(&gimbal, &gimbal_cmd);
|
||||
|
||||
Gimbal_Output(&gimbal);
|
||||
|
||||
/* USER CODE END */
|
||||
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
||||
|
||||
49
User/task/ctrl_shoot.c
Normal file
49
User/task/ctrl_shoot.c
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
ctrl_shoot Task
|
||||
|
||||
*/
|
||||
|
||||
/* Includes ----------------------------------------------------------------- */
|
||||
#include "task/user_task.h"
|
||||
/* USER INCLUDE BEGIN */
|
||||
#include "module/shoot.h"
|
||||
#include "module/config.h"
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
/* Private macro ------------------------------------------------------------ */
|
||||
/* Private variables -------------------------------------------------------- */
|
||||
/* USER STRUCT BEGIN */
|
||||
Shoot_t shoot;
|
||||
Shoot_CMD_t shoot_cmd;
|
||||
/* USER STRUCT END */
|
||||
|
||||
/* Private function --------------------------------------------------------- */
|
||||
/* Exported functions ------------------------------------------------------- */
|
||||
void Task_ctrl_shoot(void *argument) {
|
||||
(void)argument; /* 未使用argument,消除警告 */
|
||||
|
||||
|
||||
/* 计算任务运行到指定频率需要等待的tick数 */
|
||||
const uint32_t delay_tick = osKernelGetTickFreq() / CTRL_SHOOT_FREQ;
|
||||
|
||||
osDelay(CTRL_SHOOT_INIT_DELAY); /* 延时一段时间再开启任务 */
|
||||
|
||||
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
|
||||
/* USER CODE INIT BEGIN */
|
||||
Shoot_Init(&shoot,&Config_GetRobotParam()->shoot_param,CTRL_SHOOT_FREQ);
|
||||
/* USER CODE INIT END */
|
||||
|
||||
while (1) {
|
||||
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
||||
/* USER CODE BEGIN */
|
||||
osMessageQueueGet(task_runtime.msgq.shoot.shoot_cmd, &shoot_cmd, NULL, 0);
|
||||
Shoot_UpdateFeedback(&shoot);
|
||||
// Shoot_Test(&shoot);
|
||||
Shoot_Control(&shoot,&shoot_cmd);
|
||||
/* USER CODE END */
|
||||
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,10 +7,11 @@
|
||||
#include "task/user_task.h"
|
||||
|
||||
/* USER INCLUDE BEGIN */
|
||||
#include "component/ahrs.h"
|
||||
#include "module/config.h"
|
||||
#include "device/motor.h"
|
||||
#include "module/balance_chassis.h"
|
||||
/* USER INCLUDE BEGIN */
|
||||
#include "module/gimbal.h"
|
||||
#include "module/shoot.h"
|
||||
/* USER INCLUDE END */
|
||||
|
||||
/* Private typedef ---------------------------------------------------------- */
|
||||
/* Private define ----------------------------------------------------------- */
|
||||
@ -37,12 +38,22 @@ void Task_Init(void *argument) {
|
||||
task_runtime.thread.atti_esti = osThreadNew(Task_atti_esti, NULL, &attr_atti_esti);
|
||||
task_runtime.thread.ctrl_chassis = osThreadNew(Task_ctrl_chassis, NULL, &attr_ctrl_chassis);
|
||||
task_runtime.thread.ctrl_gimbal = osThreadNew(Task_ctrl_gimbal, NULL, &attr_ctrl_gimbal);
|
||||
task_runtime.thread.ctrl_shoot = osThreadNew(Task_ctrl_shoot, NULL, &attr_ctrl_shoot);
|
||||
task_runtime.thread.monitor = osThreadNew(Task_monitor, NULL, &attr_monitor);
|
||||
task_runtime.thread.ai = osThreadNew(Task_ai, NULL, &attr_ai);
|
||||
task_runtime.thread.cmd = osThreadNew(Task_cmd, NULL, &attr_cmd);
|
||||
|
||||
// 创建消息队列
|
||||
/* USER MESSAGE BEGIN */
|
||||
task_runtime.msgq.user_msg= osMessageQueueNew(2u, 10, NULL);
|
||||
task_runtime.msgq.chassis_imu = osMessageQueueNew(2u, sizeof(Chassis_IMU_t), NULL);
|
||||
task_runtime.msgq.Chassis_cmd = osMessageQueueNew(2u, sizeof(Chassis_CMD_t), NULL);
|
||||
task_runtime.msgq.chassis.imu = osMessageQueueNew(2u, sizeof(Chassis_IMU_t), NULL);
|
||||
task_runtime.msgq.chassis.cmd = osMessageQueueNew(2u, sizeof(Chassis_CMD_t), NULL);
|
||||
task_runtime.msgq.chassis.yaw = osMessageQueueNew(2u, sizeof(MOTOR_Feedback_t), NULL);
|
||||
task_runtime.msgq.gimbal.imu= osMessageQueueNew(2u, sizeof(Gimbal_IMU_t), NULL);
|
||||
task_runtime.msgq.gimbal.cmd= osMessageQueueNew(2u, sizeof(Gimbal_CMD_t), NULL);
|
||||
task_runtime.msgq.shoot.shoot_cmd = osMessageQueueNew(2u, sizeof(Shoot_CMD_t), NULL);
|
||||
task_runtime.msgq.ai.quat = osMessageQueueNew(2u, sizeof(AHRS_Quaternion_t), NULL);
|
||||
task_runtime.msgq.ai.move_vec = osMessageQueueNew(2u, sizeof(MoveVector_t), NULL);
|
||||
/* USER MESSAGE END */
|
||||
|
||||
osKernelUnlock(); // 解锁内核
|
||||
|
||||
44
User/task/monitor.c
Normal file
44
User/task/monitor.c
Normal file
@ -0,0 +1,44 @@ | ||||