Merge branch 'master'

This commit is contained in:
Kidenygood 2023-04-03 20:14:24 +08:00
commit 607615e6b6
52 changed files with 903 additions and 418 deletions

11
.vscode/launch.json vendored
View File

@ -6,7 +6,7 @@
{
"name": "Debug-DAP",
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}\\build\\${workspaceFolderBasename}.elf", // ,vscode
"executable": "${workspaceRoot}\\build\\basic_framework.elf", // ,vscode
"request": "launch",
"type": "cortex-debug",
//使J-link GDB Server;GBD ServerSVD
@ -19,14 +19,16 @@
"configFiles": [
"openocd_dap.cfg", // ,,openocdshare/scripts
],
"runToEntryPoint": "main" // main
"runToEntryPoint": "main", // main
//"preLaunchTask": "build task",//Build,使
//"preLaunchTask": "log", // RTT viewer
// log,使logprelaunch tasklogdepends on
},
// 使j-link
{
"name": "Debug-Jlink",
"cwd": "${workspaceFolder}",
"executable": "${workspaceRoot}\\build\\${workspaceFolderBasename}.elf",
"executable": "${workspaceRoot}\\build\\basic_framework.elf",
"request": "launch",
"type": "cortex-debug",
"device": "STM32F407IG",
@ -35,7 +37,10 @@
"servertype": "jlink",
"interface": "swd",
"svdFile": "STM32F407.svd",
"rtos": "FreeRTOS",
// "preLaunchTask": "build task",//Build,使
//"preLaunchTask": "log", // RTT viewer
// log,使logprelaunch tasklogdepends on
},
],
}

18
.vscode/tasks.json vendored
View File

@ -14,9 +14,9 @@
},
{
"label": "download dap",
"type": "shell",
"command": "mingw32-make download_dap",
"group": {
"type": "shell", // ,command
"command":"mingw32-make download_dap", // "mingw32-make -j24 ; mingw32-make download_dap",
"group": { // ,,使.
"kind": "build",
"isDefault": false
}
@ -24,11 +24,21 @@
{
"label": "download jlink",
"type": "shell",
"command": "mingw32-make download_jlink",
"command":"mingw32-make download_jlink", // "mingw32-make -j24 ; mingw32-make download_dap"
"group": {
"kind": "build",
"isDefault": false
}
},
{
"label": "log",
"type": "shell",
"command":"JlinkRTTClient",
"args": [],
"problemMatcher": [],
// "dependsOn":[
// "build task", // .
// ]
}
]
}

View File

@ -1,5 +1,7 @@
# 异常报告
已知可能出现的bug将会列在此处并指明修复期限和任务执行者。
使用中遇到的bug和错误放在此处。参照下列格式
## 标题用简短的一句话描述
@ -32,6 +34,8 @@
## 使用LK电机并挂载在hcan2上时会出现HardFault
> 已修复此问题。修复日志请查看当前目录下的“如何定位bug.md”。
使用MF9025v2电机,并将其配置在CAN2上。经过一次LKMotorControl第二次进入时hcan->instance会在HAL_CAN_Add_Tx_Message()结束时被未知的语句修改成奇怪的值造成HardFault
### 尝试解决的方案
@ -45,3 +49,21 @@
### 紧急程度
⭐⭐⭐⭐⭐
## 总线挂载多个电机后,pitch和yaw的GM6020电机出现编码器反馈值跳动
> 已修复详细信息见“如何定位bug.md”
CAN1总线挂载5个电机,4\*3508+1\*6020,控制报文发送频率为500Hz,电机的反馈频率皆为1kHz.云台在控制时会出现突然跳动.添加到Ozone graph查看发现ECD(编码器)值在静止状态下也会出现突然抖动,并且幅度超过4000.但不会出现超过编码器反馈值范围的值.
### 尝试解决的方案
若使用单个6020电机,不会出现此问题. 曾认为是指针越界导致`motor_measure->ecd`值被修改, 需要进一步观察其他反馈值是否出现问题. 且反馈值始终在编码器范围之内.
### 如何复现问题
同时启用CAN1和CAN2并在单条CAN总线上挂载超过5个电机.
### 紧急程度
⭐⭐⭐

View File

@ -122,7 +122,7 @@ void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* CAN1 interrupt Init */
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 6, 0);
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
@ -155,7 +155,7 @@ void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* CAN2 interrupt Init */
HAL_NVIC_SetPriority(CAN2_RX0_IRQn, 6, 0);
HAL_NVIC_SetPriority(CAN2_RX0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(CAN2_RX0_IRQn);
HAL_NVIC_SetPriority(CAN2_RX1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(CAN2_RX1_IRQn);

View File

@ -31,7 +31,6 @@
#include "daemon.h"
#include "robot.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/

View File

@ -46,7 +46,7 @@ void MX_USART1_UART_Init(void)
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.BaudRate = 921600;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;

View File

@ -8,7 +8,6 @@
>
> 1. 添加一键编译+启用ozone调试脚本使得整个进一步流程自动化
> 2. 增加更多的背景知识介绍
> 3. 增加VSCode下RTT viewer的支持
@ -481,6 +480,12 @@ VSCode `ctrl+,`进入设置,通过`搜索`找到cortex-debug插件的设置。
### RTT Viewer日志功能
本框架添加了vscode下Segger RTT client的支持。在`.vscode/task.json`中已经添加了启动rtt viewer client的任务。你也可以将此任务作为附加启动任务和调试一起启动方便查看日志。要使用日志请包含`bsp_log.h`。注意需要将jlink的安装目录添加到环境变量中。
### 更好的编辑体验
建议安装以下插件:
@ -595,7 +600,7 @@ Project.SetOSPlugin(“plugin_name”)
2. 变量watch窗口这里的变量不会实时更新只有在暂停或遇到断点的时候才会更新。若希望实时查看在这里右键选择需要动态查看的变量选择Graph他就会出现在**窗口8**的位置。
如果不需要可视化查看变量变化的趋势,但是想不赞同查看变量的值请右键点击变量选择一个合适的refresh rate
如果不需要可视化查看变量变化的趋势,但是想不暂停查看变量的值请右键点击变量选择一个合适的refresh rate
![image-20221119173731119](assets/image-20221119173731119.png)
@ -654,7 +659,7 @@ Project.SetOSPlugin(“plugin_name”)
在Terminal窗口查看还可以通过命令直接控制单片机的运行不过不常用
未打开窗口则在view-> terminal中打开。
未打开窗口则在view-> terminal中打开。使用bsp_log打印的日志会输出到该窗口中.
- **外设查看**

View File

@ -1,4 +1,10 @@
// app
#include "balance.h"
#include "vmc_project.h"
#include "gain_table.h"
#include "robot_def.h"
#include "general_def.h"
// module
#include "HT04.h"
#include "LK9025.h"
#include "bmi088.h"
@ -6,9 +12,8 @@
#include "super_cap.h"
#include "controller.h"
#include "can_comm.h"
// standard
#include "stdint.h"
#include "robot_def.h"
#include "general_def.h"
#include "arm_math.h" // 需要用到较多三角函数
/* 底盘拥有的模块实例 */
@ -55,15 +60,15 @@ static void CalcLQR()
*
*/
static void VMCProject()
{
{ // 拟将功能封装到vmc_project.h中
}
/**
* @brief :
*
*/
static PIDInstance swerving_pid;
static PIDInstance anti_crash_pid;
static PIDInstance swerving_pid; // 转向PID,有转向指令时使用IMU的加速度反馈积分以获取速度和位置状态量
static PIDInstance anti_crash_pid; // 抗劈叉,将输出以相反的方向叠加到左右腿的上
static void SynthesizeMotion()
{
@ -73,15 +78,16 @@ static void SynthesizeMotion()
* @brief : + roll轴补偿(),PD模拟弹簧的传递函数
*
*/
static PIDInstance leg_length_pid;
static PIDInstance roll_compensate_pid;
static PIDInstance leg_length_pid; // 用PD模拟弹簧的传递函数,不需要积分项(弹簧是一个无积分的二阶系统),增益不可过大否则抗外界冲击响应时太"硬"
static PIDInstance roll_compensate_pid; // roll轴补偿,用于保持机体水平
static void LegControl()
{
}
/**
* @brief ?
* @brief ??
*
*
*/
static void FlyDetect()
@ -89,7 +95,7 @@ static void FlyDetect()
}
/**
* @brief
* @brief ,
*
*/
static void WattLimit()
@ -242,6 +248,7 @@ void BalanceInit()
PIDInit(&roll_compensate_pid, &roll_compensate_pid_conf);
}
/* balanceTask可能需要以更高频率运行,以提高线性化的精确程度 */
void BalanceTask()
{
}

View File

@ -2,13 +2,14 @@
#pragma once
#include "stdint.h"
#include "stm32f407xx.h"
#include "arm_math.h"
#include "math.h"
#define GAIN_TABLE_SIZE 100 // 增益表大小
// K 2x6,6个状态变量2个输出(Tp关节电机和T驱动轮电机)
static float leglen2gain [GAIN_TABLE_SIZE][2][6] = {};
static float leglen2gain [GAIN_TABLE_SIZE][2][6] = {0};
static interpolation_flag = 0; // 插值方式:1 线性插值 0 关闭插值

View File

@ -0,0 +1,14 @@
#ifndef VMC_PROJECT_H
#define VMC_PROJECT_H
#include "stm32f407xx.h"
#include "arm_math.h"
#include "math.h"
#include "general_def.h"
// 将五连杆和直杆的vmc映射放在此处,方便修改和调试,balance.c不会太长
#endif // !VMC_PROJECT_H

View File

@ -17,8 +17,9 @@
#include "super_cap.h"
#include "message_center.h"
// referee需要移动到module层
/////////////////////////
#include "referee.h"
#include "rm_referee.h"
/////////////////////////
@ -67,16 +68,20 @@ void ChassisInit()
.can_init_config.can_handle = &hcan1,
.controller_param_init_config = {
.speed_PID = {
.Kp = 10,
.Ki = 0,
.Kd = 0,
.MaxOut = 4000,
.Kp = 10,//4.5
.Ki = 0,//0
.Kd = 0,//0
.IntegralLimit = 3000,
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit |PID_Derivative_On_Measurement,
.MaxOut = 12000,
},
.current_PID = {
.Kp = 1,
.Ki = 0,
.Kp = 0.5,//0.4
.Ki = 0,//0
.Kd = 0,
.MaxOut = 4000,
.IntegralLimit = 3000,
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit |PID_Derivative_On_Measurement,
.MaxOut = 15000,
},
},
.controller_setting_init_config = {
@ -88,20 +93,20 @@ void ChassisInit()
.motor_type = M3508,
};
// @todo: 当前还没有设置电机的正反转,仍然需要手动添加reference的正负号,需要电机module的支持,待修改.
chassis_motor_config.can_init_config.tx_id = 4;
chassis_motor_config.controller_setting_init_config.reverse_flag = MOTOR_DIRECTION_NORMAL;
chassis_motor_config.can_init_config.tx_id = 1;
chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
motor_lf = DJIMotorInit(&chassis_motor_config);
chassis_motor_config.can_init_config.tx_id = 3,
chassis_motor_config.controller_setting_init_config.reverse_flag = MOTOR_DIRECTION_NORMAL;
chassis_motor_config.can_init_config.tx_id = 2;
chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
motor_rf = DJIMotorInit(&chassis_motor_config);
chassis_motor_config.can_init_config.tx_id = 1,
chassis_motor_config.controller_setting_init_config.reverse_flag = MOTOR_DIRECTION_NORMAL;
chassis_motor_config.can_init_config.tx_id = 4;
chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
motor_lb = DJIMotorInit(&chassis_motor_config);
chassis_motor_config.can_init_config.tx_id = 2,
chassis_motor_config.controller_setting_init_config.reverse_flag = MOTOR_DIRECTION_NORMAL;
chassis_motor_config.can_init_config.tx_id = 3;
chassis_motor_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
motor_rb = DJIMotorInit(&chassis_motor_config);
// referee_data = RefereeInit(&huart6); // 裁判系统初始化
@ -219,10 +224,10 @@ void ChassisTask()
chassis_cmd_recv.wz = 0;
break;
case CHASSIS_FOLLOW_GIMBAL_YAW: // 跟随云台,不单独设置pid,以误差角度平方为速度输出
chassis_cmd_recv.wz = 0.05f * powf(chassis_cmd_recv.wz, 2.0f);
chassis_cmd_recv.wz = -1.5*chassis_cmd_recv.offset_angle*abs(chassis_cmd_recv.offset_angle);
break;
case CHASSIS_ROTATE: // 自旋,同时保持全向机动;当前wz维持定值,后续增加不规则的变速策略
// chassis_cmd_recv.wz=sin(t)
chassis_cmd_recv.wz=4000;
break;
default:
break;

View File

@ -1,5 +1,7 @@
// app
#include "robot_def.h"
#include "robot_cmd.h"
// module
#include "remote_control.h"
#include "ins_task.h"
#include "master_process.h"
@ -66,6 +68,7 @@ void RobotCMDInit()
};
cmd_can_comm = CANCommInit(&comm_conf);
#endif // GIMBAL_BOARD
gimbal_cmd_send.pitch = 0;
robot_state = ROBOT_READY; // 启动时机器人进入工作模式,后续加入所有应用初始化完成之后再进入
}
@ -105,9 +108,15 @@ static void RemoteControlSet()
{
// 控制底盘和云台运行模式,云台待添加,云台是否始终使用IMU数据?
if (switch_is_down(rc_data[TEMP].rc.switch_right)) // 右侧开关状态[下],底盘跟随云台
chassis_cmd_send.chassis_mode = CHASSIS_FOLLOW_GIMBAL_YAW;
{
chassis_cmd_send.chassis_mode = CHASSIS_ROTATE;
gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
}
else if (switch_is_mid(rc_data[TEMP].rc.switch_right)) // 右侧开关状态[中],底盘和云台分离,底盘保持不转动
{
chassis_cmd_send.chassis_mode = CHASSIS_NO_FOLLOW;
gimbal_cmd_send.gimbal_mode = GIMBAL_FREE_MODE;
}
// 云台参数,确定云台控制数据
if (switch_is_mid(rc_data[TEMP].rc.switch_left)) // 左侧开关状态为[中],视觉模式
@ -118,9 +127,17 @@ static void RemoteControlSet()
// 左侧开关状态为[下],或视觉未识别到目标,纯遥控器拨杆控制
if (switch_is_down(rc_data[TEMP].rc.switch_left) || vision_recv_data->target_state == NO_TARGET)
{ // 按照摇杆的输出大小进行角度增量,增益系数需调整
gimbal_cmd_send.yaw += 0.0015f * (float)rc_data[TEMP].rc.rocker_l_;
gimbal_cmd_send.pitch += 0.002f * (float)rc_data[TEMP].rc.rocker_l1;
gimbal_cmd_send.gimbal_mode = GIMBAL_FREE_MODE;
gimbal_cmd_send.yaw += 0.005f * (float)rc_data[TEMP].rc.rocker_l_;
gimbal_cmd_send.pitch += 0.001f * (float)rc_data[TEMP].rc.rocker_l1;
// 摇杆控制的软件限位
// if (gimbal_cmd_send.pitch <= PITCH_MIN_ECD)
// {
// gimbal_cmd_send.pitch = PITCH_MIN_ECD;
// }
// else if (gimbal_cmd_send.pitch >= PITCH_MAX_ECD)
// {
// gimbal_cmd_send.pitch = PITCH_MAX_ECD;
// }
}
// 底盘参数,目前没有加入小陀螺(调试似乎暂时没有必要),系数需要调整
@ -138,13 +155,13 @@ static void RemoteControlSet()
shoot_cmd_send.friction_mode = FRICTION_ON;
else
shoot_cmd_send.friction_mode = FRICTION_OFF;
// 拨弹控制,目前固定为连发
// 拨弹控制,遥控器固定为一种拨弹模式,可自行选择
if (rc_data[TEMP].rc.dial < -500)
shoot_cmd_send.load_mode = LOAD_BURSTFIRE;
else
shoot_cmd_send.load_mode = LOAD_STOP;
// 射频控制,固定每秒1发,后续可以根据左侧拨轮的值大小切换射频,
shoot_cmd_send.shoot_rate = 1;
shoot_cmd_send.shoot_rate = 8;
}
/**
@ -153,8 +170,83 @@ static void RemoteControlSet()
*/
static void MouseKeySet()
{
// 待添加键鼠控制
// ...
chassis_cmd_send.vx = rc_data[TEMP].key[KEY_PRESS].w * 300 - rc_data[TEMP].key[KEY_PRESS].s * 300; // 系数待测
chassis_cmd_send.vy = rc_data[TEMP].key[KEY_PRESS].s * 300 - rc_data[TEMP].key[KEY_PRESS].d * 300;
gimbal_cmd_send.yaw += (float)rc_data[TEMP].mouse.x / 660 * 10; // 系数待测
gimbal_cmd_send.pitch += (float)rc_data[TEMP].mouse.y / 660 * 10;
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_Z] % 3) // Z键设置弹速
{
case 0:
shoot_cmd_send.bullet_speed = 15;
break;
case 1:
shoot_cmd_send.bullet_speed = 18;
break;
default:
shoot_cmd_send.bullet_speed = 30;
break;
}
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_E] % 4) // E键设置发射模式
{
case 0:
shoot_cmd_send.load_mode = LOAD_STOP;
break;
case 1:
shoot_cmd_send.load_mode = LOAD_1_BULLET;
break;
case 2:
shoot_cmd_send.load_mode = LOAD_3_BULLET;
break;
default:
shoot_cmd_send.load_mode = LOAD_BURSTFIRE;
break;
}
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_R] % 2) // R键开关弹舱
{
case 0:
shoot_cmd_send.lid_mode = LID_OPEN;
break;
default:
shoot_cmd_send.lid_mode = LID_CLOSE;
break;
}
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_F] % 2) // F键开关摩擦轮
{
case 0:
shoot_cmd_send.friction_mode = FRICTION_OFF;
break;
default:
shoot_cmd_send.friction_mode = FRICTION_ON;
break;
}
switch (rc_data[TEMP].key_count[KEY_PRESS][Key_C] % 4) // C键设置底盘速度
{
case 0:
chassis_cmd_send.chassis_speed_buff = 40;
break;
case 1:
chassis_cmd_send.chassis_speed_buff = 60;
break;
case 2:
chassis_cmd_send.chassis_speed_buff = 80;
break;
default:
chassis_cmd_send.chassis_speed_buff = 100;
break;
}
switch (rc_data[TEMP].key[KEY_PRESS].shift) //待添加 按shift允许超功率 消耗缓冲能量
{
case 1:
break;
default:
break;
}
}
/**
@ -173,6 +265,8 @@ static void EmergencyHandler()
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
chassis_cmd_send.chassis_mode = CHASSIS_ZERO_FORCE;
shoot_cmd_send.shoot_mode = SHOOT_OFF;
shoot_cmd_send.friction_mode = FRICTION_OFF;
shoot_cmd_send.load_mode = LOAD_STOP;
}
// 遥控器右侧开关为[上],恢复正常运行
if (switch_is_up(rc_data[TEMP].rc.switch_right))
@ -180,6 +274,22 @@ static void EmergencyHandler()
robot_state = ROBOT_READY;
shoot_cmd_send.shoot_mode = SHOOT_ON;
}
switch (rc_data[TEMP].key_count[KEY_PRESS_WITH_CTRL][Key_C]%2) //ctrl+c 进入急停
{
case 0:
robot_state = ROBOT_READY;
shoot_cmd_send.shoot_mode = SHOOT_ON;
break;
default:
robot_state = ROBOT_STOP;
gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
chassis_cmd_send.chassis_mode = CHASSIS_ZERO_FORCE;
shoot_cmd_send.shoot_mode = SHOOT_OFF;
shoot_cmd_send.friction_mode = FRICTION_OFF;
shoot_cmd_send.load_mode = LOAD_STOP;
break;
}
}
/* 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率) */
@ -197,7 +307,6 @@ void RobotCMDTask()
// 根据gimbal的反馈值计算云台和底盘正方向的夹角,不需要传参,通过static私有变量完成
CalcOffsetAngle();
// 根据遥控器左侧开关,确定当前使用的控制模式为遥控器调试还是键鼠
if (switch_is_down(rc_data[TEMP].rc.switch_left)) // 遥控器左侧开关状态为[下],遥控器控制
RemoteControlSet();
@ -207,11 +316,12 @@ void RobotCMDTask()
EmergencyHandler(); // 处理模块离线和遥控器急停等紧急情况
// 设置视觉发送数据,还需增加加速度和角速度数据
vision_send_data.bullet_speed = chassis_fetch_data.bullet_speed;
vision_send_data.enemy_color = chassis_fetch_data.enemy_color;
vision_send_data.bullet_speed = 15;
vision_send_data.enemy_color = 0;
vision_send_data.pitch = gimbal_fetch_data.gimbal_imu_data.Pitch;
vision_send_data.yaw = gimbal_fetch_data.gimbal_imu_data.Yaw;
vision_send_data.roll = gimbal_fetch_data.gimbal_imu_data.Roll;
;
// 推送消息,双板通信,视觉通信等
// 其他应用所需的控制数据在remotecontrolsetmode和mousekeysetmode中完成设置

View File

@ -55,7 +55,7 @@ void GimbalInit()
.work_mode = BMI088_BLOCK_PERIODIC_MODE,
};
// imu=BMI088Register(&imu_config);
// gimba_IMU_data = INS_Init(); // IMU先初始化,获取姿态数据指针赋给yaw电机的其他数据来源
gimba_IMU_data = INS_Init(); // IMU先初始化,获取姿态数据指针赋给yaw电机的其他数据来源
// YAW
Motor_Init_Config_s yaw_config = {
.can_init_config = {
@ -64,60 +64,68 @@ void GimbalInit()
},
.controller_param_init_config = {
.angle_PID = {
.Kp = 20,
.Kp = 8, //8
.Ki = 0,
.Kd = 0,
.MaxOut = 2000,
.DeadBand = 0.3,
.DeadBand = 0.1,
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit |PID_Derivative_On_Measurement,
.IntegralLimit = 100,
.MaxOut = 500,
},
.speed_PID = {
.Kp = 10,
.Ki = 0,
.Kp = 50,//50
.Ki = 200,//200
.Kd = 0,
.MaxOut = 4000,
.Improve = PID_Trapezoid_Intergral |PID_Integral_Limit |PID_Derivative_On_Measurement,
.IntegralLimit = 3000,
.MaxOut = 20000,
},
.other_angle_feedback_ptr = &gimba_IMU_data->YawTotalAngle,
// 还需要增加角速度额外反馈指针,注意方向,ins_task.md中有c板的bodyframe坐标系说明
// .other_speed_feedback_ptr=&gimba_IMU_data.wz;
.other_speed_feedback_ptr=&gimba_IMU_data->Gyro[2],
},
.controller_setting_init_config = {
.angle_feedback_source = MOTOR_FEED,
.speed_feedback_source = MOTOR_FEED,
.angle_feedback_source = OTHER_FEED,
.speed_feedback_source = OTHER_FEED,
.outer_loop_type = ANGLE_LOOP,
.close_loop_type = ANGLE_LOOP | SPEED_LOOP,
.reverse_flag = MOTOR_DIRECTION_NORMAL,
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
},
.motor_type = GM6020};
// PITCH
Motor_Init_Config_s pitch_config = {
.can_init_config = {
.can_handle = &hcan1,
.can_handle = &hcan2,
.tx_id = 2,
},
.controller_param_init_config = {
.angle_PID = {
.Kp = 30,
.Kp = 10, // 10
.Ki = 0,
.Kd = 0,
.MaxOut = 4000,
.DeadBand = 0.3,
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit | PID_Derivative_On_Measurement,
.IntegralLimit = 100,
.MaxOut = 500,
},
.speed_PID = {
.Kp = 10,
.Ki = 0,
.Kd = 0,
.MaxOut = 4000,
.Kp=50,//50
.Ki =350,//350
.Kd =0,//0
.Improve = PID_Trapezoid_Intergral | PID_Integral_Limit |PID_Derivative_On_Measurement,
.IntegralLimit =2500,
.MaxOut = 20000,
},
.other_angle_feedback_ptr = &gimba_IMU_data->Pitch,
// 还需要增加角速度额外反馈指针,注意方向,ins_task.md中有c板的bodyframe坐标系说明
// .other_speed_feedback_ptr=&gimba_IMU_data.wy,
.other_speed_feedback_ptr = (&gimba_IMU_data->Gyro[0]),
},
.controller_setting_init_config = {
.angle_feedback_source = MOTOR_FEED,
.speed_feedback_source = MOTOR_FEED,
.angle_feedback_source = OTHER_FEED,
.speed_feedback_source = OTHER_FEED,
.outer_loop_type = ANGLE_LOOP,
.close_loop_type = ANGLE_LOOP | SPEED_LOOP,
.reverse_flag = MOTOR_DIRECTION_NORMAL,
.close_loop_type = SPEED_LOOP | ANGLE_LOOP,
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
},
.motor_type = GM6020,
};
@ -128,7 +136,7 @@ void GimbalInit()
gimbal_pub = PubRegister("gimbal_feed", sizeof(Gimbal_Upload_Data_s));
gimbal_sub = SubRegister("gimbal_cmd", sizeof(Gimbal_Ctrl_Cmd_s));
}
int aaaaaaa;
/* 机器人云台控制核心任务,后续考虑只保留IMU控制,不再需要电机的反馈 */
void GimbalTask()
{
@ -136,6 +144,7 @@ void GimbalTask()
// 后续增加未收到数据的处理
SubGetMessage(gimbal_sub, &gimbal_cmd_recv);
// @todo:现在已不再需要电机反馈,实际上可以始终使用IMU的姿态数据来作为云台的反馈,yaw电机的offset只是用来跟随底盘
// 根据控制模式进行电机反馈切换和过渡,视觉模式在robot_cmd模块就已经设置好,gimbal只看yaw_ref和pitch_ref
switch (gimbal_cmd_recv.gimbal_mode)
{
@ -159,16 +168,23 @@ void GimbalTask()
case GIMBAL_FREE_MODE: // 后续删除,或加入云台追地盘的跟随模式(响应速度更快)
DJIMotorEnable(yaw_motor);
DJIMotorEnable(pitch_motor);
DJIMotorChangeFeed(yaw_motor, ANGLE_LOOP, MOTOR_FEED);
DJIMotorChangeFeed(yaw_motor, SPEED_LOOP, MOTOR_FEED);
DJIMotorChangeFeed(pitch_motor, ANGLE_LOOP, MOTOR_FEED);
DJIMotorChangeFeed(pitch_motor, SPEED_LOOP, MOTOR_FEED);
DJIMotorChangeFeed(yaw_motor, ANGLE_LOOP, OTHER_FEED);
DJIMotorChangeFeed(yaw_motor, SPEED_LOOP, OTHER_FEED);
DJIMotorChangeFeed(pitch_motor, ANGLE_LOOP, OTHER_FEED);
DJIMotorChangeFeed(pitch_motor, SPEED_LOOP, OTHER_FEED);
DJIMotorSetRef(yaw_motor, gimbal_cmd_recv.yaw); // yaw和pitch会在robot_cmd中处理好多圈和单圈
DJIMotorSetRef(pitch_motor, gimbal_cmd_recv.pitch);
break;
default:
break;
}
// if(yaw_motor->motor_measure.total_angle>120)
// {
// aaaaaaa++;
// }
// 在合适的地方添加pitch重力补偿前馈力矩
// 根据IMU姿态/pitch电机角度反馈计算出当前配重下的重力矩
// ...
// 设置反馈数据,主要是imu和yaw的ecd
gimbal_feedback_data.gimbal_imu_data = *gimba_IMU_data;

View File

@ -0,0 +1,3 @@
# referee
需要将此模块移动到module层并新建一个rtos任务以一定频率运行用于ui刷新和多机通信

View File

@ -35,7 +35,7 @@ void RobotInit()
#endif
#if defined(ONE_BOARD) || defined(CHASSIS_BOARD)
Referee_Interactive_init();
// Referee_Interactive_init();
ChassisInit();
#endif
// 初始化完成,开启中断

View File

@ -34,21 +34,27 @@
/* 机器人重要参数定义,注意根据不同机器人进行修改,浮点数需要以.0或f结尾,无符号以u结尾 */
// 云台参数
#define YAW_CHASSIS_ALIGN_ECD 4000 // 云台和底盘对齐指向相同方向时的电机编码器值,若对云台有机械改动需要修改
#define YAW_CHASSIS_ALIGN_ECD 2711 // 云台和底盘对齐指向相同方向时的电机编码器值,若对云台有机械改动需要修改
#define YAW_ECD_GREATER_THAN_4096 0 // ALIGN_ECD值是否大于4096,是为1,否为0;用于计算云台偏转角度
#define PITCH_HORIZON_ECD 0 // 云台处于水平位置时编码器值,若对云台有机械改动需要修改
#define PITCH_HORIZON_ECD 3412 // 云台处于水平位置时编码器值,若对云台有机械改动需要修改
#define PITCH_MAX_ANGLE 0 //云台竖直方向最大角度 (注意反馈如果是陀螺仪,则填写陀螺仪的角度)
#define PITCH_MIN_ANGLE 0 //云台竖直方向最小角度 (注意反馈如果是陀螺仪,则填写陀螺仪的角度)
// 发射参数
#define ONE_BULLET_DELTA_ANGLE 0 // 发射一发弹丸拨盘转动的距离,由机械设计图纸给出
#define ONE_BULLET_DELTA_ANGLE 36 // 发射一发弹丸拨盘转动的距离,由机械设计图纸给出
#define REDUCTION_RATIO_LOADER 49.0f // 拨盘电机的减速比,英雄需要修改为3508的19.0f
#define NUM_PER_CIRCLE 1 // 拨盘一圈的装载量
#define NUM_PER_CIRCLE 10 // 拨盘一圈的装载量
// 机器人底盘修改的参数,单位为mm(毫米)
#define WHEEL_BASE 300 // 纵向轴距(前进后退方向)
#define WHEEL_BASE 350 // 纵向轴距(前进后退方向)
#define TRACK_WIDTH 300 // 横向轮距(左右平移方向)
#define CENTER_GIMBAL_OFFSET_X 0 // 云台旋转中心距底盘几何中心的距离,前后方向,云台位于正中心时默认设为0
#define CENTER_GIMBAL_OFFSET_Y 0 // 云台旋转中心距底盘几何中心的距离,左右方向,云台位于正中心时默认设为0
#define RADIUS_WHEEL 60 // 轮子半径
#define REDUCTION_RATIO_WHEEL 19.0f // 电机减速比,因为编码器量测的是转子的速度而不是输出轴的速度故需进行转换
#define GYRO2GIMBAL_DIR_YAW 1 //陀螺仪数据相较于云台的yaw的方向,1为相同,-1为相反
#define GYRO2GIMBAL_DIR_PITCH 1 //陀螺仪数据相较于云台的pitch的方向,1为相同,-1为相反
#define GYRO2GIMBAL_DIR_ROLL 1 //陀螺仪数据相较于云台的roll的方向,1为相同,-1为相反
// 检查是否出现主控板定义冲突,只允许一个开发板定义存在,否则编译会自动报错
#if (defined(ONE_BOARD) && defined(CHASSIS_BOARD)) || \
(defined(ONE_BOARD) && defined(GIMBAL_BOARD)) || \
@ -128,6 +134,7 @@ typedef enum
// 功率限制,从裁判系统获取
typedef struct
{ // 功率控制
float chassis_power_mx;
} Chassis_Power_Data_s;
/* ----------------CMD应用发布的控制数据,应当由gimbal/chassis/shoot订阅---------------- */
@ -144,7 +151,7 @@ typedef struct
float wz; // 旋转速度
float offset_angle; // 底盘和归中位置的夹角
chassis_mode_e chassis_mode;
int chassis_speed_buff;
// UI部分
// ...

View File

@ -1,5 +1,6 @@
#include "shoot.h"
#include "robot_def.h"
#include "dji_motor.h"
#include "message_center.h"
#include "bsp_dwt.h"
@ -25,20 +26,23 @@ void ShootInit()
Motor_Init_Config_s friction_config = {
.can_init_config = {
.can_handle = &hcan2,
.tx_id = 6,
},
.controller_param_init_config = {
.speed_PID = {
.Kp = 10,
.Ki = 0,
.Kp = 0,//20
.Ki = 0,//1
.Kd = 0,
.MaxOut = 2000,
.Improve = PID_Integral_Limit,
.IntegralLimit = 10000,
.MaxOut = 15000,
},
.current_PID = {
.Kp = 1,
.Ki = 0,
.Kp = 0,//0.7
.Ki = 0,//0.1
.Kd = 0,
.MaxOut = 2000,
.Improve = PID_Integral_Limit,
.IntegralLimit = 10000,
.MaxOut = 15000,
},
},
.controller_setting_init_config = {
@ -47,47 +51,52 @@ void ShootInit()
.outer_loop_type = SPEED_LOOP,
.close_loop_type = SPEED_LOOP | CURRENT_LOOP,
.reverse_flag = MOTOR_DIRECTION_NORMAL,
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
},
.motor_type = M3508};
friction_config.can_init_config.tx_id = 1,
friction_l = DJIMotorInit(&friction_config);
friction_config.can_init_config.tx_id = 5; // 右摩擦轮,改txid和方向就行
friction_config.controller_setting_init_config.reverse_flag = MOTOR_DIRECTION_NORMAL;
friction_config.can_init_config.tx_id = 2; // 右摩擦轮,改txid和方向就行
friction_config.controller_setting_init_config.motor_reverse_flag = MOTOR_DIRECTION_REVERSE;
friction_r = DJIMotorInit(&friction_config);
// 拨盘电机
Motor_Init_Config_s loader_config = {
.can_init_config = {
.can_handle = &hcan2,
.tx_id = 7,
.tx_id = 3,
},
.controller_param_init_config = {
.angle_PID = {
// 如果启用位置环来控制发弹,需要较大的I值保证输出力矩的线性度否则出现接近拨出的力矩大幅下降
.Kp = 10,
.Kp = 0,//10
.Ki = 0,
.Kd = 0,
.MaxOut = 200,
},
.speed_PID = {
.Kp = 1,
.Ki = 0,
.Kp = 0,//10
.Ki = 0,//1
.Kd = 0,
.MaxOut = 2000,
.Improve = PID_Integral_Limit,
.IntegralLimit = 5000,
.MaxOut = 5000,
},
.current_PID = {
.Kp = 1,
.Ki = 0,
.Kp = 0,//0.7
.Ki = 0,//0.1
.Kd = 0,
.MaxOut = 3000,
.Improve = PID_Integral_Limit,
.IntegralLimit = 5000,
.MaxOut = 5000,
},
},
.controller_setting_init_config = {
.angle_feedback_source = MOTOR_FEED, .speed_feedback_source = MOTOR_FEED,
.outer_loop_type = SPEED_LOOP, // 初始化成SPEED_LOOP,让拨盘停在原地,防止拨盘上电时乱转
.close_loop_type = ANGLE_LOOP | SPEED_LOOP,
.reverse_flag = MOTOR_DIRECTION_NORMAL, // 注意方向设置为拨盘的拨出的击发方向
.close_loop_type = CURRENT_LOOP | SPEED_LOOP,
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL, // 注意方向设置为拨盘的拨出的击发方向
},
.motor_type = M2006 // 英雄使用m3508
};
@ -119,8 +128,8 @@ void ShootTask()
// 如果上一次触发单发或3发指令的时间加上不应期仍然大于当前时间(尚未休眠完毕),直接返回即可
// 单发模式主要提供给能量机关激活使用(以及英雄的射击大部分处于单发)
if (hibernate_time + dead_time > DWT_GetTimeline_ms())
return;
// if (hibernate_time + dead_time > DWT_GetTimeline_ms())
// return;
// 若不在休眠状态,根据robotCMD传来的控制模式进行拨盘电机参考值设定和模式切换
switch (shoot_cmd_recv.load_mode)
@ -181,8 +190,8 @@ void ShootTask()
DJIMotorSetRef(friction_r, 0);
break;
default: // 当前为了调试设定的默认值4000,因为还没有加入裁判系统无法读取弹速.
DJIMotorSetRef(friction_l, 1000);
DJIMotorSetRef(friction_r, 1000);
DJIMotorSetRef(friction_l, 30000);
DJIMotorSetRef(friction_r, 30000);
break;
}
}

View File

@ -276,9 +276,9 @@ Mcu.UserName=STM32F407IGHx
MxCube.Version=6.7.0
MxDb.Version=DB.6.0.70
NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
NVIC.CAN1_RX0_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:true\:true
NVIC.CAN1_RX0_IRQn=true\:5\:0\:true\:false\:true\:true\:true\:true\:true
NVIC.CAN1_RX1_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.CAN2_RX0_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:true\:true
NVIC.CAN2_RX0_IRQn=true\:5\:0\:true\:false\:true\:true\:true\:true\:true
NVIC.CAN2_RX1_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.DMA1_Stream1_IRQn=true\:5\:0\:false\:false\:true\:true\:false\:true\:true
NVIC.DMA1_Stream2_IRQn=true\:5\:0\:false\:false\:true\:true\:false\:true\:true
@ -432,7 +432,7 @@ PF0.Mode=I2C
PF0.Signal=I2C2_SDA
PF1.Mode=I2C
PF1.Signal=I2C2_SCL
PF6.GPIOParameters=GPIO_Label,GPIO_Speed,GPIO_PuPd
PF6.GPIOParameters=GPIO_Speed,GPIO_PuPd,GPIO_Label
PF6.GPIO_Label=IMU_TEMP
PF6.GPIO_PuPd=GPIO_PULLUP
PF6.GPIO_Speed=GPIO_SPEED_FREQ_HIGH

View File

@ -6,6 +6,7 @@
/* can instance ptrs storage, used for recv callback */
// 在CAN产生接收中断会遍历数组,选出hcan和rxid与发生中断的实例相同的那个,调用其回调函数
// @todo: 后续为每个CAN总线单独添加一个can_instance指针数组,提高回调查找的性能
static CANInstance *can_instance[CAN_MX_REGISTER_CNT] = {NULL};
static uint8_t idx; // 全局CAN实例索引,每次有新的模块注册会自增
@ -130,8 +131,8 @@ void CANSetDLC(CANInstance *_instance, uint8_t length)
*/
static void CANFIFOxCallback(CAN_HandleTypeDef *_hcan, uint32_t fifox)
{
static uint8_t can_rx_buff[8]; // 用于保存接收到的数据,static是为了减少栈空间占用,避免重复分配
static CAN_RxHeaderTypeDef rxconf; // 同上
uint8_t can_rx_buff[8];
HAL_CAN_GetRxMessage(_hcan, fifox, &rxconf, can_rx_buff); // 从FIFO中获取数据
for (size_t i = 0; i < idx; ++i)

View File

@ -15,7 +15,7 @@ static GPIOInstance *gpio_instance[GPIO_MX_DEVICE_NUM] = {NULL};
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
// 如有必要,可以根据pinstate和HAL_GPIO_ReadPin来判断是上升沿还是下降沿/rise&fall等
static GPIOInstance *gpio;
GPIOInstance *gpio;
for (size_t i = 0; i < idx; i++)
{
gpio = gpio_instance[i];

View File

@ -4,7 +4,6 @@
#include "SEGGER_RTT_Conf.h"
#include <stdio.h>
#define BUFFER_INDEX 0
void BSPLogInit()
{
@ -15,7 +14,7 @@ int PrintLog(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int n = SEGGER_RTT_vprintf(BUFFER_INDEX, fmt, &args);
int n = SEGGER_RTT_vprintf(BUFFER_INDEX, fmt, &args); // 一次可以开启多个buffer(多个终端),我们只用一个
va_end(args);
return n;
}
@ -32,3 +31,4 @@ void Float2Str(char *str, float va)
else
sprintf(str, "%d.%d", head, point);
}

View File

@ -1,18 +1,53 @@
#ifndef _BSP_LOG_H
#define _BSP_LOG_H
#include "SEGGER_RTT.h"
#include "SEGGER_RTT_Conf.h"
#include <stdio.h>
#define BUFFER_INDEX 0
/**
* @brief ,
* @brief
*
*/
void BSPLogInit();
/**
* @brief segger RTT打印日志,,printf
* @brief ,LOGI,LOGW,LOGE等使用
*
* @param fmt
* @param ...
* @return int
*/
#define LOG_PROTO(type,color,format,...) \
SEGGER_RTT_printf(BUFFER_INDEX," %s%s"format"\r\n%s", \
color, \
type, \
##__VA_ARGS__, \
RTT_CTRL_RESET)
/*------下面是日志输出的接口--------*/
/* 清屏 */
#define LOG_CLEAR() SEGGER_RTT_WriteString(0, " "RTT_CTRL_CLEAR)
/* 无颜色日志输出 */
#define LOG(format,...) LOG_PROTO("","",format,##__VA_ARGS__)
/* 有颜色格式日志输出,建议使用这些宏来输出日志 */
// information level
#define LOGINFO(format,...) LOG_PROTO("I", RTT_CTRL_TEXT_BRIGHT_GREEN , format, ##__VA_ARGS__)
// warning level
#define LOGWARNING(format,...) LOG_PROTO("W", RTT_CTRL_TEXT_BRIGHT_YELLOW, format, ##__VA_ARGS__)
// error level
#define LOGERROR(format,...) LOG_PROTO("E", RTT_CTRL_TEXT_BRIGHT_RED , format, ##__VA_ARGS__)
/**
* @brief segger RTT打印日志,,printf.
*
* @param fmt
* @param ...
* @return int log字符数
*/
int PrintLog(const char *fmt, ...);

View File

@ -25,7 +25,7 @@ static USARTInstance *usart_instance[DEVICE_USART_CNT] = {NULL};
*
* @param _instance instance owned by module,
*/
static void USARTServiceInit(USARTInstance *_instance)
void USARTServiceInit(USARTInstance *_instance)
{
HAL_UARTEx_ReceiveToIdle_DMA(_instance->usart_handle, _instance->recv_buff, _instance->recv_buff_size);
// 关闭dma half transfer中断防止两次进入HAL_UARTEx_RxEventCallback()
@ -52,25 +52,36 @@ USARTInstance *USARTRegister(USART_Init_Config_s *init_config)
}
/* @todo 当前仅进行了形式上的封装,后续要进一步考虑是否将module的行为与bsp完全分离 */
void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size)
{
HAL_UART_Transmit_DMA(_instance->usart_handle, send_buf, send_size);
}
void USARTAbort(USARTInstance *_instance, USART_TRANSFER_MODE mode)
void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size, USART_TRANSFER_MODE mode)
{
switch (mode)
{
case USART_TRANSFER_TX:
// if(_instance.work_mode == USART_TX_DMA)
HAL_UART_AbortTransmit_IT(_instance->usart_handle);
case USART_TRANSFER_BLOCKING:
HAL_UART_Transmit(_instance->usart_handle, send_buf, send_size, 100);
break;
case USART_TRANSFER_RX:
// if(_instance.work_mode == USART_RX_DMA)
HAL_UART_AbortReceive_IT(_instance->usart_handle);
case USART_TRANSFER_IT:
HAL_UART_Transmit_IT(_instance->usart_handle, send_buf, send_size);
break;
case USART_TRANSFER_NONE:
case USART_TRANSFER_DMA:
HAL_UART_Transmit_DMA(_instance->usart_handle, send_buf, send_size);
break;
default:
while (1)
; // illegal mode! check your code context! 检查定义instance的代码上下文,可能出现指针越界
break;
}
}
/* 串口发送时,gstate会被设为BUSY_TX */
uint8_t USARTIsReady(USARTInstance *_instance)
{
if(_instance->usart_handle->gState | HAL_UART_STATE_BUSY_TX)
{
return 0;
}
else
{
return 1;
}
}

View File

@ -10,11 +10,13 @@
// 模块回调函数,用于解析协议
typedef void (*usart_module_callback)();
/* 发送模式枚举 */
typedef enum
{
USART_TRANSFER_NONE=0,
USART_TRANSFER_TX,
USART_TRANSFER_RX,
USART_TRANSFER_BLOCKING,
USART_TRANSFER_IT,
USART_TRANSFER_DMA,
} USART_TRANSFER_MODE;
// 串口实例结构体,每个module都要包含一个实例.
@ -36,30 +38,38 @@ typedef struct
} USART_Init_Config_s;
/**
* @brief .
* @brief ,
*
* @param init_config
*/
USARTInstance *USARTRegister(USART_Init_Config_s *init_config);
/**
* @brief ,usart实例.lost callback的情况(使daemon)
*
* @param _instance
*/
void USARTServiceInit(USARTInstance *_instance);
/**
* @brief ,usart实例,buff以及这一帧的长度
* DMA发送,
* @todo DMA发送,
* ,
* @note ,IT/DMA会导致上一次的发送未完成而新的发送取消.
* @note 使DMA/IT进行发送,USARTIsReady()使,module实现一个发送队列和任务.
* @todo USARTInstance增加发送队列以进行连续发送?
*
* @param _instance
* @param send_buf buffer
* @param send_size how many bytes to send
*/
void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size);
void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size,USART_TRANSFER_MODE mode);
/**
* @brief ,mode参数来选择终止发送还是接收
* @brief ,IT/DMA发送
*
* @param _instance
* @param mode
* @param _instance
* @return uint8_t ready 1, busy 0
*/
void USARTAbort(USARTInstance *_instance,USART_TRANSFER_MODE mode);
uint8_t USARTIsReady(USARTInstance *_instance);
#endif

View File

@ -11,20 +11,21 @@
#include "bsp_usb.h"
static uint8_t *bsp_usb_rx_buffer; // 接收到的数据会被放在这里,buffer size为2028
static uint8_t *bsp_usb_rx_buffer; // 接收到的数据会被放在这里,buffer size为2048
// 注意usb单个数据包(Full speed模式下)最大为64byte,超出可能会出现丢包情况
// 这是传输完成的回调函数,在usbd_cdc_if.c中被调用
__weak void USBTransmitCpltCallback(uint32_t len)
{
// 本次发送的数据
// 本次发送的数据长度
UNUSED(len);
// 传输完成会调用此函数,to do something
}
// 这是接收回调函数
__weak void USBReceiveCpltCallback(uint32_t len)
{
// 本次接收的数据
// 本次接收的数据长度
UNUSED(len);
// 传输完成会调用此函数,to do something
}

View File

@ -23,8 +23,7 @@ static void CANCommResetRx(CANCommInstance *ins)
*/
static void CANCommRxCallback(CANInstance *_instance)
{
static CANCommInstance *comm;
comm = (CANCommInstance *)_instance->id; // 注意写法,将can instance的id强制转换为CANCommInstance*类型
CANCommInstance *comm = (CANCommInstance *)_instance->id; // 注意写法,将can instance的id强制转换为CANCommInstance*类型
/* 接收状态判断 */
if (_instance->rx_buff[0] == CAN_COMM_HEADER && comm->recv_state == 0) // 之前尚未开始接收且此次包里第一个位置是帧头

View File

@ -33,7 +33,7 @@ uint8_t DaemonIsOnline(DaemonInstance *instance)
void DaemonTask()
{
static DaemonInstance *dins; // 提高可读性同时降低访存开销
DaemonInstance *dins; // 提高可读性同时降低访存开销
for (size_t i = 0; i < idx; ++i)
{
dins = daemon_instances[i];

View File

@ -17,7 +17,7 @@
#include "bsp_temperature.h"
#include "spi.h"
#include "user_lib.h"
#include "general_def.h"
static INS_t INS;
static IMU_Param_t IMU_Param;
static PIDInstance TempCtrl = {0};
@ -118,7 +118,6 @@ void INS_Task(void)
}
BodyFrameToEarthFrame(INS.MotionAccel_b, INS.MotionAccel_n, INS.q); // 转换回导航系n
// 获取最终数据
INS.Yaw = QEKF_INS.Yaw;
INS.Pitch = QEKF_INS.Pitch;
INS.Roll = QEKF_INS.Roll;

View File

@ -29,9 +29,8 @@ static uint8_t ist8310_write_reg_data_error[IST8310_WRITE_REG_NUM][3] = {
*/
static void IST8310Decode(IICInstance *iic)
{
static int16_t temp[3]; // 用于存储解码后的数据
static IST8310Instance *ist; // 用于存储IST8310实例的指针
ist = (IST8310Instance *)(iic->id); // iic的id保存了IST8310实例的指针(父指针)
int16_t temp[3]; // 用于存储解码后的数据
IST8310Instance *ist= (IST8310Instance *)(iic->id); // iic的id保存了IST8310实例的指针(父指针)
memcpy(temp, ist->iic_buffer, 6 * sizeof(uint8_t)); // 不要强制转换,直接cpy
for (uint8_t i = 0; i < 3; i++)

View File

@ -10,7 +10,7 @@ LEDInstance *LEDRegister(LED_Init_Config_s *led_config)
{
LEDInstance *led_ins = (LEDInstance *)zero_malloc(sizeof(LEDInstance));
// 剩下的值暂时都被置零
led_ins->led_pwm = GPIORegister(&led_config->pwm_config);
led_ins->led_pwm = PWMRegister(&led_config->pwm_config);
led_ins->led_switch = led_config->init_swtich;
bsp_led_ins[idx++] = led_ins;

View File

@ -12,12 +12,15 @@
#include "bsp_usart.h"
#include "usart.h"
#include "seasky_protocol.h"
#include "daemon.h"
#include "bsp_log.h"
static Vision_Recv_s recv_data;
// @todo:由于后续需要进行IMU-Cam的硬件触发采集控制,因此可能需要将发送设置为定时任务,或由IMU采集完成产生的中断唤醒的任务,
// 后者显然更nice,使得时间戳对齐. 因此,在send_data中设定其他的标志位数据,让ins_task填充姿态值.
// static Vision_Send_s send_data;
static USARTInstance *vision_usart_instance;
static DaemonInstance *vision_daemon_instance;
/**
* @brief ,bsp_usart.c中被usart rx callback调用
@ -26,11 +29,24 @@ static USARTInstance *vision_usart_instance;
*/
static void DecodeVision()
{
static uint16_t flag_register;
uint16_t flag_register;
DaemonReload(vision_daemon_instance); // 喂狗
get_protocol_info(vision_usart_instance->recv_buff, &flag_register, (uint8_t *)&recv_data.pitch);
// TODO: code to resolve flag_register;
}
/**
* @brief 线,daemon.c中被daemon task调用
* @attention HAL库的设计问题,DMA接收之后同时发送有概率出现__HAL_LOCK(),使
* .daemon判断数据更新,.
*
* @param id vision_usart_instance的地址,.
*/
static void VisionOfflineCallback(void *id)
{
USARTServiceInit(vision_usart_instance);
}
/* 视觉通信初始化 */
Vision_Recv_s *VisionInit(UART_HandleTypeDef *_handle)
{
@ -38,8 +54,16 @@ Vision_Recv_s *VisionInit(UART_HandleTypeDef *_handle)
conf.module_callback = DecodeVision;
conf.recv_buff_size = VISION_RECV_SIZE;
conf.usart_handle = _handle;
vision_usart_instance = USARTRegister(&conf);
// 为master process注册daemon,用于判断视觉通信是否离线
Daemon_Init_Config_s daemon_conf = {
.callback = VisionOfflineCallback, // 离线时调用的回调函数,会重启串口接收
.owner_id = vision_usart_instance,
.reload_count = 10,
};
vision_daemon_instance = DaemonRegister(&daemon_conf);
return &recv_data;
}
@ -54,12 +78,15 @@ Vision_Recv_s *VisionInit(UART_HandleTypeDef *_handle)
*/
void VisionSend(Vision_Send_s *send)
{
static uint16_t flag_register;
static uint8_t send_buff[VISION_SEND_SIZE];
static uint16_t tx_len;
uint16_t flag_register;
uint8_t send_buff[VISION_SEND_SIZE];
uint16_t tx_len;
// TODO: code to set flag_register
flag_register = 30<<8|0b00000001;
// 将数据转化为seasky协议的数据包
get_protocol_send_data(0x02, flag_register, &send->yaw, 3, send_buff, &tx_len);
USARTSend(vision_usart_instance, send_buff, tx_len);
USARTSend(vision_usart_instance, send_buff, tx_len, USART_TRANSFER_DMA); // 和视觉通信使用IT,防止和接收使用的DMA冲突
// 此处为HAL设计的缺陷,DMASTOP会停止发送和接收,导致再也无法进入接收中断.
// 也可在发送完成中断中重新启动DMA接收,但较为复杂.因此,此处使用IT发送.
// 若使用了daemon,则也可以使用DMA发送.
}

View File

@ -4,7 +4,7 @@
#include "bsp_usart.h"
#include "seasky_protocol.h"
#define VISION_RECV_SIZE 36u // 当前为固定值,36字节
#define VISION_RECV_SIZE 18u // 当前为固定值,36字节
#define VISION_SEND_SIZE 36u
#pragma pack(1)

View File

@ -15,6 +15,11 @@
#include "crc16.h"
#include "memory.h"
/*获取CRC8校验码*/
uint8_t Get_CRC8_Check(uint8_t *pchMessage,uint16_t dwLength)
{
return crc_8(pchMessage,dwLength);
}
/*检验CRC8数据段*/
static uint8_t CRC8_Check_Sum(uint8_t *pchMessage, uint16_t dwLength)
{
@ -25,6 +30,12 @@ static uint8_t CRC8_Check_Sum(uint8_t *pchMessage, uint16_t dwLength)
return (ucExpected == pchMessage[dwLength - 1]);
}
/*获取CRC16校验码*/
uint16_t Get_CRC16_Check(uint8_t *pchMessage,uint32_t dwLength)
{
return crc_16(pchMessage,dwLength);
}
/*检验CRC16数据段*/
static uint16_t CRC16_Check_Sum(uint8_t *pchMessage, uint32_t dwLength)
{
@ -114,7 +125,7 @@ uint16_t get_protocol_info(uint8_t *rx_buf, // 接收到的原始数据
if (CRC16_Check_Sum(&rx_buf[0], date_length))
{
*flags_register = (rx_buf[7] << 8) | rx_buf[6];
memcpy(rx_data, rx_buf + 8, 4 * sizeof(pro.header.data_length - 2));
memcpy(rx_data, rx_buf + 8, pro.header.data_length - 2);
return pro.cmd_id;
}
}

View File

@ -2,78 +2,11 @@
#include "stdlib.h"
#include "string.h"
/* 消息初始化用 */
static char pname[MAX_EVENT_COUNT][MAX_EVENT_NAME_LEN + 1];
static char sname[MAX_EVENT_COUNT][MAX_EVENT_NAME_LEN + 1];
static void *p_ptr[MAX_EVENT_COUNT] = {NULL};
static void **s_pptr[MAX_EVENT_COUNT] = {NULL}; // 因为要修改指针,所以需要二重指针
/* ----------------------------------第三方指针传递版的实现,deprecated----------------------------------- */
void MessageInit()
{
// pub必须唯一,即消息名称不能重复,不得有多个pub发布相同消息名称
// 对每一个subscriber,寻找相同消息名称的publisher,可能有多个sub从相同pub获取消息
for (size_t i = 0; i < MAX_EVENT_COUNT; ++i)
{
if (s_pptr[i] != NULL)
{
for (size_t j = 0; j < MAX_EVENT_COUNT; ++j) // 遍历publisher
{
if (p_ptr[j] != NULL) // 不为空
{
if (strcmp(sname[i], pname[j]) == 0) // 比较消息名是否一致
{
*s_pptr[i] = p_ptr[j]; // 将sub的指针指向pub的数据
break;
}
}
else // 到结尾,退出
{
while (1)
; // 如果你卡在这里,说明没有找到消息发布者!请确认消息名称是否键入错误
}
}
}
else // 说明已经遍历完所有的subs
{
break;
}
}
}
/* 传入数据地址 */
void PublisherRegister(char *name, void *data)
{
static uint8_t idx;
for (size_t i = 0; i < idx; ++i)
{
if (strcmp(pname[i], name) == 0)
while (1)
; // 运行至此说明pub的消息发布名称冲突
}
strcpy(pname[idx], name);
p_ptr[idx++] = data;
}
/* 注意传入的是指针的地址,传参时使用&对数据指针取地址 */
void SubscribeEvent(char *name, void **data_ptr)
{
static uint8_t idx;
strcpy(sname[idx], name);
s_pptr[idx++] = data_ptr;
}
/* ----------------------------------链表-队列版的实现----------------------------------- */
/* message_center是fake head node,是方便链表编写的技巧,这样就不需要处理链表头的特殊情况 */
static Publisher_t message_center = {
.event_name = "Message_Manager",
.first_subs = NULL,
.next_event_node = NULL
};
.next_event_node = NULL};
static void CheckName(char *name)
{
@ -157,6 +90,7 @@ Publisher_t *PubRegister(char *name, uint8_t data_len)
if (strcmp(node->event_name, name) == 0) // 如果已经注册了相同的事件,直接返回结点指针
{
CheckLen(data_len, node->data_len);
node->pub_registered_flag = 1;
return node;
}
} // 遍历完发现尚未创建name对应的事件
@ -165,6 +99,7 @@ Publisher_t *PubRegister(char *name, uint8_t data_len)
memset(node->next_event_node, 0, sizeof(Publisher_t));
node->next_event_node->data_len = data_len;
strcpy(node->next_event_node->event_name, name);
node->pub_registered_flag = 1;
return node->next_event_node;
}

View File

@ -19,30 +19,6 @@
#define MAX_EVENT_COUNT 12 // 最多支持的事件数量
#define QUEUE_SIZE 1
/**
* @brief ,app的"回调函数"
*
*/
void MessageInit();
/**
* @brief
*
* @param name ,MAX_EVENT_NAME_LEN
* @param data
*/
void PublisherRegister(char *name, void *data);
/**
* @brief ,
*
* @param name
* @param data ,,(&)
*/
void SubscribeEvent(char *name, void **data);
#endif // !PUBSUB_H
typedef struct mqt
{
/* 用数组模拟FIFO队列 */
@ -69,6 +45,7 @@ typedef struct ent
Subscriber_t *first_subs;
/* 指向下一个Publisher的指针 */
struct ent *next_event_node;
uint8_t pub_registered_flag; // 用于标记该发布者是否已经注册
} Publisher_t;
/**
@ -105,3 +82,5 @@ uint8_t SubGetMessage(Subscriber_t *sub, void *data_ptr);
* @return uint8_t
*/
uint8_t PubPushMessage(Publisher_t *pub, void *data_ptr);
#endif // !PUBSUB_H

View File

@ -1,5 +1,6 @@
#include "dji_motor.h"
#include "general_def.h"
#include "bsp_log.h"
static uint8_t idx = 0; // register idx,是该文件的全局电机索引,在注册时使用
@ -32,16 +33,6 @@ static CANInstance sender_assignment[6] = {
*/
static uint8_t sender_enable_flag[6] = {0};
/**
* @brief id冲突时,ID
* @todo segger jlink
*/
static void IDcrash_Handler(uint8_t conflict_motor_idx, uint8_t temp_motor_idx)
{
while (1)
;
}
/**
* @brief /ID,id分配方式计算发送ID和接收ID,
* 便
@ -79,7 +70,11 @@ static void MotorSenderGrouping(DJIMotorInstance *motor, CAN_Init_Config_s *conf
for (size_t i = 0; i < idx; ++i)
{
if (dji_motor_instance[i]->motor_can_instance->can_handle == config->can_handle && dji_motor_instance[i]->motor_can_instance->rx_id == config->rx_id)
IDcrash_Handler(i, idx);
{
LOGERROR("[dji_motor] ID crash. Check in debug mode, add dji_motor_instance to watch to get more information."); // 后续可以把id和CAN打印出来
while (1)
; // 6020的id 1-4和2006/3508的id 5-8会发生冲突(若有注册,即1!5,2!6,3!7,4!8) (1!5!,LTC! (((不是)
}
}
break;
@ -103,7 +98,11 @@ static void MotorSenderGrouping(DJIMotorInstance *motor, CAN_Init_Config_s *conf
for (size_t i = 0; i < idx; ++i)
{
if (dji_motor_instance[i]->motor_can_instance->can_handle == config->can_handle && dji_motor_instance[i]->motor_can_instance->rx_id == config->rx_id)
IDcrash_Handler(i, idx);
{
LOGERROR("[dji_motor] ID crash. Check in debug mode, add dji_motor_instance to watch to get more information.");
while (1)
; // 6020的id 1-4和2006/3508的id 5-8会发生冲突(若有注册,即1!5,2!6,3!7,4!8)
}
}
break;
@ -121,13 +120,10 @@ static void MotorSenderGrouping(DJIMotorInstance *motor, CAN_Init_Config_s *conf
*/
static void DecodeDJIMotor(CANInstance *_instance)
{
// 由于需要多次变址访存,直接将buff和measure地址保存在寄存器里避免多次存取
static uint8_t *rxbuff;
static DJI_Motor_Measure_s *measure;
rxbuff = _instance->rx_buff;
// 这里对can instance的id进行了强制转换,从而获得电机的instance实例地址
// _instance指针指向的id是对应电机instance的地址,通过强制转换为电机instance的指针,再通过->运算符访问电机的成员motor_measure,最后取地址获得指针
measure = &(((DJIMotorInstance *)_instance->id)->motor_measure); // measure要多次使用,保存指针减小访存开销
uint8_t *rxbuff = _instance->rx_buff;
DJI_Motor_Measure_s *measure = &(((DJIMotorInstance *)_instance->id)->motor_measure); // measure要多次使用,保存指针减小访存开销
// 解析数据并对电流和速度进行滤波,电机的反馈报文具体格式见电机说明手册
measure->last_ecd = measure->ecd;
@ -139,7 +135,7 @@ static void DecodeDJIMotor(CANInstance *_instance)
CURRENT_SMOOTH_COEF * (float)((int16_t)(rxbuff[4] << 8 | rxbuff[5]));
measure->temperate = rxbuff[6];
// 多圈角度计算,前提是两次采样间电机转过的角度小于180°,高速转动时可能会出现问题,自己画个图就清楚计算过程了
// 多圈角度计算,前提是假设两次采样间电机转过的角度小于180°,自己画个图就清楚计算过程了
if (measure->ecd - measure->last_ecd > 4096)
measure->total_round--;
else if (measure->ecd - measure->last_ecd < -4096)
@ -191,8 +187,7 @@ void DJIMotorChangeFeed(DJIMotorInstance *motor, Closeloop_Type_e loop, Feedback
}
else
{
while (1)
; // LOOP TYPE ERROR!!!检查是否传入了正确的LOOP类型,或发生了指针越界
LOGERROR("[dji_motor] loop type error, check memory access and func param"); // 检查是否传入了正确的LOOP类型,或发生了指针越界
}
}
@ -221,15 +216,14 @@ void DJIMotorSetRef(DJIMotorInstance *motor, float ref)
// 为所有电机实例计算三环PID,发送控制报文
void DJIMotorControl()
{
// 预先通过静态变量定义避免反复释放分配栈空间,直接保存一次指针引用从而减小访存的开销
// 同样可以提高可读性
static uint8_t group, num; // 电机组号和组内编号
static int16_t set; // 电机控制CAN发送设定值
static DJIMotorInstance *motor;
static Motor_Control_Setting_s *motor_setting; // 电机控制参数
static Motor_Controller_s *motor_controller; // 电机控制器
static DJI_Motor_Measure_s *motor_measure; // 电机测量值
static float pid_measure, pid_ref; // 电机PID测量值和设定值
// 直接保存一次指针引用从而减小访存的开销,同样可以提高可读性
uint8_t group, num; // 电机组号和组内编号
int16_t set; // 电机控制CAN发送设定值
DJIMotorInstance *motor;
Motor_Control_Setting_s *motor_setting; // 电机控制参数
Motor_Controller_s *motor_controller; // 电机控制器
DJI_Motor_Measure_s *motor_measure; // 电机测量值
float pid_measure, pid_ref; // 电机PID测量值和设定值
// 遍历所有电机实例,进行串级PID的计算并设置发送报文的值
for (size_t i = 0; i < idx; ++i)
@ -239,7 +233,8 @@ void DJIMotorControl()
motor_controller = &motor->motor_controller;
motor_measure = &motor->motor_measure;
pid_ref = motor_controller->pid_ref; // 保存设定值,防止motor_controller->pid_ref在计算过程中被修改
if (motor_setting->motor_reverse_flag == MOTOR_DIRECTION_REVERSE)
pid_ref*= -1; // 设置反转
// pid_ref会顺次通过被启用的闭环充当数据的载体
// 计算位置环,只有启用位置环且外层闭环为位置时会计算速度环输出
if ((motor_setting->close_loop_type & ANGLE_LOOP) && motor_setting->outer_loop_type == ANGLE_LOOP)
@ -271,8 +266,7 @@ void DJIMotorControl()
// 获取最终输出
set = (int16_t)pid_ref;
if (motor_setting->reverse_flag == MOTOR_DIRECTION_REVERSE)
set *= -1; // 设置反转
// 分组填入发送数据
group = motor->sender_group;

View File

@ -7,6 +7,10 @@
> 1. 给不同的电机设置不同的低通滤波器惯性系数而不是统一使用宏
> 2. 为M2006和M3508增加开环的零位校准函数
---
> 建议将电机的反馈频率通过RoboMaster Assistant统一设置为500Hz。当前默认的`MotorTask()`执行频率为500Hz若不修改电机反馈频率可能导致单条总线挂载的电机数量有限且容易出现帧错误和仲裁失败的情况。
## 总览和封装说明
> 如果你不需要理解该模块的工作原理,你只需要查看这一小节。
@ -19,7 +23,7 @@ dji_motor模块对DJI智能电机包括M2006M3508以及GM6020进行了详
2. ==速度环为角速度,单位为**度/每秒**deg/sec==
3. ==电流环为mA==
3. ==电流环为A==
4. ==GM6020的输入设定为**力矩**,待测量(-30000~30000==
@ -138,7 +142,7 @@ Motor_Init_Config_s config = {
.outer_loop_type = SPEED_LOOP,
.close_loop_type = SPEED_LOOP | CURRENT_LOOP,
.speed_feedback_source = MOTOR_FEED,
.reverse_flag = MOTOR_DIRECTION_NORMAL},
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL},
// 电流环和速度环PID参数的设置,不采用计算优化则不需要传入Improve参数
// 不使用其他数据来源(如IMU),不需要传入反馈数据变量指针
.controller_param_init_config = {.current_PID = {.Improve = 0,
@ -241,7 +245,7 @@ typedef struct
{
Closeloop_Type_e outer_loop_type;
Closeloop_Type_e close_loop_type;
Reverse_Flag_e reverse_flag;
Motor_Reverse_Flag_e motor_reverse_flag;
Feedback_Source_e angle_feedback_source;
Feedback_Source_e speed_feedback_source;
} Motor_Control_Setting_s;
@ -268,14 +272,14 @@ typedef struct
> 注意务必分清串级控制多环和外层闭环的区别。前者是为了提高内环的性能使得其能更好地跟随外环参考值而后者描述的是系统真实的控制目标闭环目标。如3508没有电流环仍然可以对速度完成闭环对于高层的应用来说它们本质上不关心电机内部是否还有电流环它们只把外层闭环为速度的电机当作一个**速度伺服执行器****外层闭环**描述的就是真正的闭环目标。
- 为了避开恼人的正负号,提高代码的可维护性,在初始化电机时设定`reverse_flag`使得所有电机都按照你想要的方向旋转,其定义如下:
- 为了避开恼人的正负号,提高代码的可维护性,在初始化电机时设定`motor_reverse_flag`使得所有电机都按照你想要的方向旋转,其定义如下:
```c
typedef enum
{
MOTOR_DIRECTION_NORMAL = 0,
MOTOR_DIRECTION_REVERSE = 1
} Reverse_Flag_e;
} Motor_Reverse_Flag_e;
```
- `speed_feedback_source`以及`angle_feedback_source`是指示电机反馈来源的标志位。一般情况下电机使用自身的编码器作为控制反馈量。但在某些时候如小陀螺模式云台电机会使用IMU的姿态数据作为反馈数据来源。其定义如下
@ -433,7 +437,7 @@ Motor_Init_Config_s config = {
.outer_loop_type = SPEED_LOOP,
.close_loop_type = SPEED_LOOP | ANGLE_LOOP,
.speed_feedback_source = MOTOR_FEED,
.reverse_flag = MOTOR_DIRECTION_NORMAL
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL
},
.controller_param_init_config = {
.angle_PID = {

View File

@ -40,12 +40,9 @@ static float uint_to_float(int x_int, float x_min, float x_max, int bits)
*/
static void HTMotorDecode(CANInstance *motor_can)
{
static uint16_t tmp; // 用于暂存解析值,稍后转换成float数据,避免多次创建临时变量
static HTMotor_Measure_t *measure;
static uint8_t *rxbuff;
rxbuff = motor_can->rx_buff;
measure = &((HTMotorInstance *)motor_can->id)->motor_measure; // 将can实例中保存的id转换成电机实例的指针
uint16_t tmp; // 用于暂存解析值,稍后转换成float数据,避免多次创建临时变量
uint8_t *rxbuff = motor_can->rx_buff;
HTMotor_Measure_t *measure = &((HTMotorInstance *)motor_can->id)->motor_measure; // 将can实例中保存的id转换成电机实例的指针
measure->last_angle = measure->total_angle;
@ -89,12 +86,12 @@ void HTMotorSetRef(HTMotorInstance *motor, float ref)
void HTMotorControl()
{
static float set, pid_measure, pid_ref;
static uint16_t tmp;
static HTMotorInstance *motor;
static HTMotor_Measure_t *measure;
static Motor_Control_Setting_s *setting;
static CANInstance *motor_can;
float set, pid_measure, pid_ref;
uint16_t tmp;
HTMotorInstance *motor;
HTMotor_Measure_t *measure;
Motor_Control_Setting_s *setting;
CANInstance *motor_can;
// 遍历所有电机实例,计算PID
for (size_t i = 0; i < idx; i++)
@ -137,7 +134,7 @@ void HTMotorControl()
}
set = pid_ref;
if (setting->reverse_flag == MOTOR_DIRECTION_REVERSE)
if (setting->motor_reverse_flag == MOTOR_DIRECTION_REVERSE)
set *= -1;
LIMIT_MIN_MAX(set, T_MIN, T_MAX); // 限幅,实际上这似乎和pid输出限幅重复了

View File

@ -13,8 +13,8 @@ static CANInstance *sender_instance; // 多电机发送时使用的caninstance(
*/
static void LKMotorDecode(CANInstance *_instance)
{
static LKMotor_Measure_t *measure;
static uint8_t *rx_buff;
LKMotor_Measure_t *measure;
uint8_t *rx_buff;
rx_buff = _instance->rx_buff;
measure = &(((LKMotorInstance *)_instance->id)->measure); // 通过caninstance保存的id获取对应的motorinstance
@ -72,11 +72,11 @@ LKMotorInstance *LKMotorInit(Motor_Init_Config_s *config)
/* 第一个电机的can instance用于发送数据,向其tx_buff填充数据 */
void LKMotorControl()
{
static float pid_measure, pid_ref;
static int16_t set;
static LKMotorInstance *motor;
static LKMotor_Measure_t *measure;
static Motor_Control_Setting_s *setting;
float pid_measure, pid_ref;
int16_t set;
LKMotorInstance *motor;
LKMotor_Measure_t *measure;
Motor_Control_Setting_s *setting;
for (size_t i = 0; i < idx; ++i)
{
@ -113,7 +113,7 @@ void LKMotorControl()
}
set = pid_ref;
if (setting->reverse_flag == MOTOR_DIRECTION_REVERSE)
if (setting->motor_reverse_flag == MOTOR_DIRECTION_REVERSE)
set *= -1;
// 这里随便写的,为了兼容多电机命令.后续应该将tx_id以更好的方式表达电机id,单独使用一个CANInstance,而不是用第一个电机的CANInstance
memcpy(sender_instance->tx_buff + (motor->motor_can_ins->tx_id - 0x280) * 2, &set, sizeof(uint16_t));

View File

@ -54,8 +54,14 @@ typedef enum
{
MOTOR_DIRECTION_NORMAL = 0,
MOTOR_DIRECTION_REVERSE = 1
} Reverse_Flag_e;
} Motor_Reverse_Flag_e;
/* 反馈量正反标志 */
typedef enum
{
FEEDBACK_DIRECTION_NORMAL = 0,
FEEDBACK_DIRECTION_REVERSE = 1
} Feedback_Reverse_Flag_e;
typedef enum
{
MOTOR_STOP = 0,
@ -67,7 +73,8 @@ typedef struct
{
Closeloop_Type_e outer_loop_type; // 最外层的闭环,未设置时默认为最高级的闭环
Closeloop_Type_e close_loop_type; // 使用几个闭环(串级)
Reverse_Flag_e reverse_flag; // 是否反转
Motor_Reverse_Flag_e motor_reverse_flag; // 是否反转
Feedback_Reverse_Flag_e feedback_reverse_flag; // 反馈是否反向
Feedback_Source_e angle_feedback_source; // 角度反馈类型
Feedback_Source_e speed_feedback_source; // 速度反馈类型
Feedfoward_Type_e feedforward_flag; // 前馈标志

View File

@ -82,7 +82,7 @@ void Servo_Motor_Type_Select(ServoInstance *Servo_Motor, int16_t mode)
*/
void ServeoMotorControl()
{
static ServoInstance *Servo_Motor;
ServoInstance *Servo_Motor;
for (size_t i = 0; i < servo_idx; i++)
{

View File

@ -0,0 +1,115 @@
#include "ps_handle.h"
uint8_t PS2_RawData[9] = {0};
PS2_Instance PS2_Data = {0};
void PS2_CS(uint8_t Val)
{
if (Val)
HAL_GPIO_WritePin(PS2_CS_GPIOx, PS2_CS_Pin, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(PS2_CS_GPIOx, PS2_CS_Pin, GPIO_PIN_RESET);
}
void PS2_CLK(uint8_t Val)
{
if (Val)
HAL_GPIO_WritePin(PS2_CLK_GPIOx, PS2_CLK_Pin, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(PS2_CLK_GPIOx, PS2_CLK_Pin, GPIO_PIN_RESET);
}
void PS2_DO(uint8_t Val)
{
if (Val)
HAL_GPIO_WritePin(PS2_DO_GPIOx, PS2_DO_Pin, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(PS2_DO_GPIOx, PS2_DO_Pin, GPIO_PIN_RESET);
}
uint8_t PS2_Read_DI()
{
return HAL_GPIO_ReadPin(PS2_DI_GPIOx, PS2_DI_Pin);
}
void PS2_Delay()
{
for (int i = 0; i < 0xBf; i++)
__NOP();
}
uint8_t PS2_ReadWrite_Byte(uint8_t TxData)
{
uint8_t TX = TxData;
uint8_t RX = 0;
for (int i = 0; i < 8; i++)
{
if (TX & 0x01)
PS2_DO(1);
else
PS2_DO(0);
TX >>= 1;
PS2_CLK(1);
PS2_Delay();
PS2_CLK(0);
RX >>= 1;
RX |= (PS2_Read_DI() << 7);
PS2_Delay();
PS2_CLK(1);
PS2_Delay();
}
return RX;
}
static void PS2_Decode()
{
if (PS2_RawData[2] == 0x5A)
{
PS2_Data.Key_Select = (~PS2_RawData[3] >> 0) & 0x01; //选择键
PS2_Data.Key_Start = (~PS2_RawData[3] >> 3) & 0x01; //开始键
//左侧按键
PS2_Data.Key_L_Up = (~PS2_RawData[3] >> 4) & 0x01;
PS2_Data.Key_L_Right = (~PS2_RawData[3] >> 5) & 0x01;
PS2_Data.Key_L_Down = (~PS2_RawData[3] >> 6) & 0x01;
PS2_Data.Key_L_Left = (~PS2_RawData[3] >> 7) & 0x01;
//后侧按键
PS2_Data.Key_L2 = (~PS2_RawData[4] >> 0) & 0x01;
PS2_Data.Key_R2 = (~PS2_RawData[4] >> 1) & 0x01;
PS2_Data.Key_L1 = (~PS2_RawData[4] >> 2) & 0x01;
PS2_Data.Key_R1 = (~PS2_RawData[4] >> 3) & 0x01;
//右侧按键
PS2_Data.Key_R_Up = (~PS2_RawData[4] >> 4) & 0x01;
PS2_Data.Key_R_Right = (~PS2_RawData[4] >> 5) & 0x01;
PS2_Data.Key_R_Down = (~PS2_RawData[4] >> 6) & 0x01;
PS2_Data.Key_R_Left = (~PS2_RawData[4] >> 7) & 0x01;
if (PS2_RawData[1] == 0x41)
{ //无灯模式(摇杆值八向)
PS2_Data.Rocker_LX = 127 * (PS2_Data.Key_L_Right - PS2_Data.Key_L_Left);
PS2_Data.Rocker_LY = 127 * (PS2_Data.Key_L_Up - PS2_Data.Key_L_Down);
PS2_Data.Rocker_RX = 127 * (PS2_Data.Key_R_Right - PS2_Data.Key_R_Left);
PS2_Data.Rocker_RY = 127 * (PS2_Data.Key_R_Up - PS2_Data.Key_R_Down);
}
else if (PS2_RawData[1] == 0x73)
{ //红灯模式(摇杆值模拟)
//摇杆按键
PS2_Data.Key_Rocker_Left = (~PS2_RawData[3] >> 1) & 0x01;
PS2_Data.Key_Rocker_Right = (~PS2_RawData[3] >> 2) & 0x01;
//摇杆值
PS2_Data.Rocker_LX = PS2_RawData[7] - 0x80;
PS2_Data.Rocker_LY = -1 - (PS2_RawData[8] - 0x80);
PS2_Data.Rocker_RX = PS2_RawData[5] - 0x80;
PS2_Data.Rocker_RY = -1 - (PS2_RawData[6] - 0x80);
}
}
}
void PS2_Read_Data(void)
{
PS2_CS(0);
PS2_RawData[0] = PS2_ReadWrite_Byte(0x01); // 0
PS2_RawData[1] = PS2_ReadWrite_Byte(0x42); // 1
for (int i = 2; i < 9; i++)
PS2_RawData[i] = PS2_ReadWrite_Byte(0xff);
PS2_CS(1);
PS2_Decode();
}

View File

@ -0,0 +1,38 @@
#ifndef PS_HANDLE_H
#define PS_HANDLE_H
#include "bsp_spi.h"
#include "bsp_gpio.h"
#include "bsp_dwt.h"
#define PS2_CS_GPIOx GPIOB
#define PS2_CS_Pin GPIO_PIN_12
#define PS2_CLK_GPIOx GPIOB
#define PS2_CLK_Pin GPIO_PIN_13
#define PS2_DO_GPIOx GPIOB
#define PS2_DO_Pin GPIO_PIN_15
#define PS2_DI_GPIOx GPIOB
#define PS2_DI_Pin GPIO_PIN_14
typedef struct
{
uint8_t A_D; //模拟(红灯)为1 数字(无灯)为0
int8_t Rocker_RX, Rocker_RY, Rocker_LX, Rocker_LY; //摇杆值(模拟状态为实际值0-0xFF)(数字态为等效的值0,0x80,0xFF)
//按键值0为未触发,1为触发态
uint8_t Key_L1, Key_L2, Key_R1, Key_R2; //后侧大按键
uint8_t Key_L_Right, Key_L_Left, Key_L_Up, Key_L_Down; //左侧按键
uint8_t Key_R_Right, Key_R_Left, Key_R_Up, Key_R_Down; //右侧按键
uint8_t Key_Select; //选择键
uint8_t Key_Start; //开始键
uint8_t Key_Rocker_Left, Key_Rocker_Right; //摇杆按键
} PS2_Instance;
#endif // !PS_HANDLE_H#define PS_HANDLE_H

View File

@ -0,0 +1,3 @@
# referee
当前模块组织较为混乱,后续统一为多机通信+裁判系统信息接收+UI绘制。UI绘制和多机通信的发送部分在referee任务中以一定的频率运行信息的接收通过中断完成。

View File

@ -52,7 +52,7 @@ void RefereeLoadToBuffer(uint8_t *send, uint16_t tx_len)
*/
void RefereeSend(uint8_t *send, uint16_t tx_len)
{
USARTSend(referee_usart_instance, send, tx_len);//syhtodo此函数需要重写
USARTSend(referee_usart_instance, send, tx_len,USART_TRANSFER_IT);//syhtodo此函数需要重写
/* syhtodo DMA请求过快会导致数据发送丢失考虑数据尽可能打成一个整包以及队列发送并且发送函数添加缓冲区 */
}

View File

@ -32,9 +32,10 @@ static void RectifyRCjoystick()
* @param[out] rc_ctrl: remote control data struct point
* @retval none
*/
uint16_t aaaaa;
static void sbus_to_rc(const uint8_t *sbus_buf)
{
memcpy(&rc_ctrl[1], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断
// 摇杆,直接解算时减去偏置
rc_ctrl[TEMP].rc.rocker_r_ = ((sbus_buf[0] | (sbus_buf[1] << 8)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 0
rc_ctrl[TEMP].rc.rocker_r1 = (((sbus_buf[1] >> 3) | (sbus_buf[2] << 5)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 1
@ -53,32 +54,51 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
rc_ctrl[TEMP].mouse.press_l = sbus_buf[12]; //!< Mouse Left Is Press ?
rc_ctrl[TEMP].mouse.press_r = sbus_buf[13]; //!< Mouse Right Is Press ?
// 按键值,每个键1bit,key_temp共16位;按键顺序在remote_control.h的宏定义中可见
// 使用位域后不再需要这一中间操作
rc_ctrl[TEMP].key_temp = sbus_buf[14] | (sbus_buf[15] << 8); //!< KeyBoard value
// 位域的按键值解算,直接memcpy即可,注意小端低字节在前,即lsb在第一位,msb在最后. 尚未测试
*(uint16_t *)&rc_ctrl[TEMP].key[KEY_PRESS] = (uint16_t)(sbus_buf[14] | (sbus_buf[15] << 8));
// @todo 似乎可以直接用位域操作进行,把key_temp通过强制类型转换变成key类型? 位域方案在下面,尚未测试
// 按键值解算,利用宏+循环减少代码长度
for (uint16_t i = 0x0001, j = 0; i != 0x8000; i *= 2, j++) // 依次查看每一个键
if (rc_ctrl[TEMP].key[KEY_PRESS].ctrl)
rc_ctrl[TEMP].key[KEY_PRESS_WITH_CTRL] = rc_ctrl[TEMP].key[KEY_PRESS];
else
memset(&rc_ctrl[TEMP].key[KEY_PRESS_WITH_CTRL], 0, sizeof(Key_t));
if (rc_ctrl[TEMP].key[KEY_PRESS].shift)
rc_ctrl[TEMP].key[KEY_PRESS_WITH_SHIFT] = rc_ctrl[TEMP].key[KEY_PRESS];
else
memset(&rc_ctrl[TEMP].key[KEY_PRESS_WITH_SHIFT], 0, sizeof(Key_t));
for (uint32_t i = 0, j = 0x1; i < 16; j <<= 1, i++)
{
// 如果键按下,对应键的key press状态置1,否则为0
rc_ctrl[TEMP].key[KEY_PRESS][j] = rc_ctrl[TEMP].key_temp & i;
// 如果当前按下且上一次没按下,切换按键状态.一些模式要通过按键状态而不是按键是否按下来确定(实际上是大部分)
rc_ctrl[TEMP].key[KEY_STATE][j] = rc_ctrl[TEMP].key[KEY_PRESS][j] && !rc_ctrl[1].key[KEY_PRESS][j];
// 检查是否有组合键按下
if (rc_ctrl[TEMP].key_temp & 0x0001u << Key_Shift) // 按下ctrl
rc_ctrl[TEMP].key[KEY_PRESS_WITH_SHIFT][j] = rc_ctrl[TEMP].key_temp & i;
if (rc_ctrl[TEMP].key_temp & 0x0001u << Key_Ctrl) // 按下shift
rc_ctrl[TEMP].key[KEY_PRESS_WITH_CTRL][j] = rc_ctrl[TEMP].key_temp & i;
if (((*(uint16_t *)&rc_ctrl[TEMP].key[KEY_PRESS] & j) == j) && ((*(uint16_t *)&rc_ctrl[1].key[KEY_PRESS] & j) == 0) && ((*(uint16_t *)&rc_ctrl[TEMP].key[KEY_PRESS_WITH_CTRL] & j) != j) && ((*(uint16_t *)&rc_ctrl[TEMP].key[KEY_PRESS_WITH_SHIFT] & j) != j))
{
rc_ctrl[TEMP].key_count[KEY_PRESS][i]++;
if (rc_ctrl[TEMP].key_count[KEY_PRESS][i] >= 240)
{
rc_ctrl[TEMP].key_count[KEY_PRESS][i] = 0;
}
// 位域的按键值解算,直接memcpy即可,注意小端低字节在前,即lsb在第一位,msb在最后. 尚未测试
// *(uint16_t *)&rc_ctrl[TEMP].key_test[KEY_PRESS] = (uint16_t)(sbus_buf[14] | (sbus_buf[15] << 8));
// *(uint16_t *)&rc_ctrl[TEMP].key_test[KEY_STATE] = *(uint16_t *)&rc_ctrl[TEMP].key_test[KEY_PRESS] & ~(*(uint16_t *)&(rc_ctrl[1].key_test[KEY_PRESS]));
// if (rc_ctrl[TEMP].key_test[KEY_PRESS].ctrl)
// rc_ctrl[TEMP].key_test[KEY_PRESS_WITH_CTRL] = rc_ctrl[TEMP].key_test[KEY_PRESS];
// if (rc_ctrl[TEMP].key_test[KEY_PRESS].shift)
// rc_ctrl[TEMP].key_test[Key_Shift] = rc_ctrl[TEMP].key_test[KEY_PRESS];
}
if (((*(uint16_t *)&rc_ctrl[TEMP].key[KEY_PRESS_WITH_CTRL] & j) == j) && ((*(uint16_t *)&rc_ctrl[1].key[KEY_PRESS_WITH_CTRL] & j) == 0))
{
rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_CTRL][i]++;
if (rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_CTRL][i] >= 240)
{
rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_CTRL][i] = 0;
}
}
if (((*(uint16_t *)&rc_ctrl[TEMP].key[KEY_PRESS_WITH_SHIFT] & j) == j) && ((*(uint16_t *)&rc_ctrl[1].key[KEY_PRESS_WITH_SHIFT] & j) == 0))
{
rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_SHIFT][i]++;
if (rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_SHIFT][i] >= 240)
{
rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_SHIFT][i] = 0;
}
}
}
memcpy(&rc_ctrl[1], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断
}
/**
@ -92,12 +112,13 @@ static void RemoteControlRxCallback()
}
/**
* @brief
* @brief 线,,线
*
*/
static void RCLostCallback()
static void RCLostCallback(void *id)
{
// @todo 遥控器丢失的处理
USARTServiceInit(rc_usart_instance); // 尝试重新启动接收
}
RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle)
@ -111,8 +132,8 @@ RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle)
// 进行守护进程的注册,用于定时检查遥控器是否正常工作
// @todo 当前守护进程直接在这里注册,后续考虑将其封装到遥控器的初始化函数中,即可以让用户决定reload_count的值(是否有必要?)
Daemon_Init_Config_s daemon_conf = {
.reload_count = 100, // 100ms,遥控器的接收频率实际上是1000/14Hz(大约70)
.callback = NULL, // 后续考虑重新启动遥控器对应串口的传输
.reload_count = 10, // 100ms未收到数据视为离线,遥控器的接收频率实际上是1000/14Hz(大约70Hz)
.callback = RCLostCallback,
.owner_id = NULL, // 只有1个遥控器,不需要owner_id
};
rc_daemon_instance = DaemonRegister(&daemon_conf);
@ -126,5 +147,4 @@ uint8_t RemotecontrolIsOnline()
if (rc_init_flag)
return DaemonIsOnline(rc_daemon_instance);
return 0;
}

View File

@ -24,8 +24,8 @@
// 获取按键操作
#define KEY_PRESS 0
#define KEY_STATE 1
#define KEY_PRESS_WITH_CTRL 2
#define KEY_PRESS_WITH_SHIFT 3
#define KEY_PRESS_WITH_CTRL 1
#define KEY_PRESS_WITH_SHIFT 2
// 检查接收值是否出错
#define RC_CH_VALUE_MIN ((uint16_t)364)
@ -42,6 +42,9 @@
#define switch_is_up(s) (s == RC_SW_UP)
#define LEFT_SW 1 // 左侧开关
#define RIGHT_SW 0 // 右侧开关
//键盘状态的宏
#define key_is_press(s) (s == 1)
#define key_not_press(s) (s == 0)
/* ----------------------- PC Key Definition-------------------------------- */
// 对应key[x][0~16],获取对应的键;例如通过key[KEY_PRESS][Key_W]获取W键是否按下,后续改为位域后删除
@ -106,10 +109,9 @@ typedef struct
uint8_t press_r;
} mouse;
uint16_t key_temp;
uint8_t key[4][16]; // 当前使用的键盘索引
// Key_t key_test[4]; // 改为位域后的键盘索引,空间减少8倍,速度增加16~倍
Key_t key[3]; // 改为位域后的键盘索引,空间减少8倍,速度增加16~倍
uint8_t key_count[3][16];
} RC_ctrl_t;
/* ------------------------- Internal Data ----------------------------------- */

View File

@ -13,8 +13,8 @@ static SuperCapInstance *super_cap_instance = NULL; // 可以由app保存此指
static void SuperCapRxCallback(CANInstance *_instance)
{
static uint8_t *rxbuff;
static SuperCap_Msg_s *Msg;
uint8_t *rxbuff;
SuperCap_Msg_s *Msg;
rxbuff = _instance->rx_buff;
Msg = &super_cap_instance->cap_msg;
Msg->vol = (uint16_t)(rxbuff[0] << 8 | rxbuff[1]);

View File

@ -85,6 +85,89 @@ long long的范围比float小。无符号和有符号数直接转换可能变成
**宏只在当前文件生效**,如果宏放在.c那么对其他的文件是不可见的这也一般称作私有宏。
## 典型debug案例
这是一个结合了软件和硬件且多模块耦合的异常。
## 典型debug案例一
这是一个结合了软件和硬件且有多模块耦合的异常。该bug发生在调试平衡步兵的底盘过程当中。
### 引发bug的原因
1. 指针在强制类型转换中变成了错误的类型,使得指向的内存地址被错误地修改
2. CAN总线负载过大导致电机反馈消息丢失
这里是发生bug的代码片段:
```c
static void LKMotorDecode(CANInstance *_instance)
{
static LKMotor_Measure_t *measure;
static uint8_t *rx_buff;
rx_buff = _instance->rx_buff;
measure = &((LKMotorInstance *)_instance)->measure; // 通过caninstance保存的id获取对应的motorinstance
// 上面一行应为: measure = &(((LKMotorInstance *)_instance->id)->measure);
measure->last_ecd = measure->ecd;
measure->ecd = ...
// ....
}
```
这是问题1的出处。can instance中保存了父指针即拥有该instance的LKMotorInstance。这里想通过强制类型转换将`void*`类型的`id`转换成电机的instance指针类型并访问其measure成员变量以从CAN反馈的报文中更新量测值。然而却直接将can instance转换成motor instance。
随后更新之后的数据被覆写到can instance内部使得其成员变量改变包括hcan、txbuf、rxbuf、tx/rxlen等。hcan是HAL定义的can句柄类型里面保存了指向can状态和控制寄存器的指针以及其他HAL状态信息然而其值被电机反馈回来的值覆写之后HAL的接口访问hcan时将引起异常。
第二个问题则不是显式存在的:
```c
void MotorControlTask()
{
DJIMotorControl();
HTMotorControl();
LKMotorControl();
ServeoMotorControl();
StepMotorControl();
}
```
这是motortask的内容此任务将以500hz的频率运行。在发生bug时我们将4个HT04电机和2个LK MF9025电机全部连接到CAN1上。注意HT04不支持多电机指令因此占用的带宽较大。在`LKMotorControl()`完成参考值计算和CAN发送之后立刻会调用`HTMotorControl()`后者需要连续发送4条报文。而HT和LK电机都会在接收到控制指令之后发送反馈信息报文。由于HT电机的控制在LK电机控制之后立刻执行导致总线被占据LK电机发送的反馈数据仲裁失败无法获得总线占有权使得主机收不到反馈数据。
### bug的发现和定位的尝试
程序的大体情况如下当时进行轮足式倒立摆机器人的测试启用了balance.c在其中注册了4个HT04电机can1和2个LK9025电机can2。控制报文的发送频率均为500Hz。
测试时发现9025电机可以接收到mcu发送的控制指令并响应但是mcu始终无法获得反馈值`LKMotorInstance->measure`的所有成员变量一直是零。由于CAN是总线架构电机能接收到数据说明通信正常。HT04电机也可以正常控制并收到反馈信息。在`LKMotorDecode()`函数中添加断点发现能够成功进入1~2次随后便引发HardFault。
此时内心有些动摇开始检查硬件连线。我们尝试把LK电机也挂载到CAN1总线上。开始单步调试发现LK电机可以正常接收一次反馈报文之后就进入`Hardfault_handler()`。HT和DJI电机均无此问题。进一步进行每条指令的调试发现在成功接收到一次报文之后接收报文指的是can发生中断并在处理函数中调用LK电机的解码函数我们并没有查看measure值是否刷新实际上这时候反馈值仍然为零进入该电机的控制报文发送时通过在`Hardfault_handler()`中添加汇编语句`asm("bx lr")`,即跳转到最后一次执行的指令,发现访问`hcan->state`会引起硬件错误。遇到这种情况说明发生了越界访问或使用了野指针。检查hcan的值发现是一个非常大的地址。因此怀疑hcan指针被其他的内存访问语句修改。
有了方向之后进一步对每一个函数都进行单步进入调试同时时刻监测hcan1的值。然而这时候出现即使一开始就单步调试也无法进入LK电机解码函数的问题。于是怀疑是CAN过滤器的配置问题使得LK电机反馈报文被过滤检查LK的接收id无误后认为可能由于LK电机的发送和接收ID都比较大0x140和0x280CAN标准ID放不下。但是查阅CAN specification后发现standar ID可以容纳11位的值应该不会有问题。于是把过滤器配置为mask模式让bxCAN控制器接收所有报文即不进行过滤。然而还是不奏效仍然无法收到数据。
这时候想起HT电机是不支持多电机控制指令的因此500Hz的控制频率似乎有些过高相当于2ms内要完成2x4+1+2=11次CAN报文的发送。计算1M波特率下最大通信速率果然超出了负载。于是降低`MotorTask()`的频率为200Hz果然能重新接收到数据了。
继续单步调试,终于发现在`LKMotorDecode()`中通过强制类型转换获取LKMotorInstance的时候用错了变量使得反馈值被写入电机的`CANInstance`内导致hcan指向随机的地址最终造成访问时引发hardfault。
修改之后将LK电机挂载到CAN2上控制频率回到500Hz程序正常运行。
### 解决方案
均衡总线负载,调节任务运行时间。
# 典型debug案例二
这仍然是一个CAN总线引发的bug。使用的电机均为DJI电机。当多个电机接入时会产生反馈值跳变的情况。起初认为**总线负载过高**控制频率为500Hz反馈频率均为1kHz计算之后得出CAN的负载率接近90%),但将电机减少为一半甚至更少时仍然出现此问题。**单独使用CAN1且仅挂载一个电机则问题消失**同时使用CAN1和CAN2不论单个总线挂载几个电机则问题再次出现。
**单步调试发现反馈值并未因指针越界而被纂改**。仔细检查代码的计算发现并未出错打开Ozone查看反馈值曲线发现确实偶发跳变但跳变值并未超出反馈值范围即即使发生跳变值仍然在**正常范围内**,因此不像是总线负载过大导致数据帧错误或指针越界修改的随机值。加入多个电机同时查看反馈值,**发现反馈跳变之后会和另一电机的反馈值相同**呈现“你跳到我我跳到你”的图景。怀疑CAN中断被**重入**即一个中断未完成时另一个CAN报文到来打断了当前的中断并执行了**相同的反馈解码函数**。但CAN1和CAN2的中断优先级均为5因此不可能打断彼此。打开CubeMX查看初始化配置发现两个CAN的FIFO0和FIFO1中断优先级不同分别是5和6。则FIFO1的溢出中断会被FIFO0打断且我们在电机的解码函数中使用了一些**静态变量**用于存储触发接收中断的电机报文的相关信息,故而新进入的中断覆写了之前中断的静态变量值,使得之前中断在恢复之后存储了前者的值,导致自身反馈错误。
将优先级统一设为5编译之后重新运行反馈值正常。
> “同时使用CAN1和CAN2不论几个电机则问题再次出现。” 导致此问题的原因是初始化CAN时按照rxid分配FIFO因此注册的电机会被交替分配到不同的FIFO故不论注册了几个电机只要多于2、注册到哪条总线都会出现FIFO1中断被FIFO0打断的情况。

View File

@ -14,5 +14,7 @@
## 请给你编写的bsp和module提供详细的文档和使用示例并为接口增加安全检查
“treat your user as idot
用于调试的条件编译和log输出也是必须的。
另外“treat your user as idot