Merge branch 'master'

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

View File

@@ -65,10 +65,10 @@ typedef struct
// improve parameter
PID_Improvement_e Improve;
float IntegralLimit; // 积分限幅
float CoefA; // 变速积分 For Changing Integral
float CoefB; // 变速积分 ITerm = Err*((A-abs(err)+B)/A) when B<|err|<A+B
float Output_LPF_RC; // 输出滤波器 RC = 1/omegac
float IntegralLimit; // 积分限幅
float CoefA; // 变速积分 For Changing Integral
float CoefB; // 变速积分 ITerm = Err*((A-abs(err)+B)/A) when B<|err|<A+B
float Output_LPF_RC; // 输出滤波器 RC = 1/omegac
float Derivative_LPF_RC; // 微分滤波器系数
//-----------------------------------

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)
{
@@ -112,18 +45,18 @@ Subscriber_t *SubRegister(char *name, uint8_t data_len)
{ // 给消息队列的每一个元素分配空间,queue里保存的实际上是数据执指针,这样可以兼容不同的数据长度
ret->queue[i] = malloc(sizeof(data_len));
}
//如果是第一个订阅者,特殊处理一下
if(node->first_subs==NULL)
// 如果是第一个订阅者,特殊处理一下
if (node->first_subs == NULL)
{
node->first_subs=ret;
node->first_subs = ret;
return ret;
}
// 遍历订阅者链表,直到到达尾部
Subscriber_t *sub = node->first_subs; // 作为iterator
while (sub->next_subs_queue) // 遍历订阅了该事件的订阅者链表
while (sub->next_subs_queue) // 遍历订阅了该事件的订阅者链表
{
sub = sub->next_subs_queue; // 移动到下一个订阅者,遇到空指针停下,说明到了链表尾部
}
}
sub->next_subs_queue = ret; // 把刚刚创建的订阅者接上
return ret;
}
@@ -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;
}
@@ -189,7 +124,7 @@ uint8_t PubPushMessage(Publisher_t *pub, void *data_ptr)
while (iter)
{
if (iter->temp_size == QUEUE_SIZE) // 如果队列已满,则需要删除最老的数据(头部),再填入
{
{
// 队列头索引前移动,相当于抛弃前一个位置的数据,被抛弃的位置稍后会被写入新的数据
iter->front_idx = (iter->front_idx + 1) % QUEUE_SIZE;
iter->temp_size--; // 相当于出队,size-1

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;
}
}
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即可,注意小端低字节在前,即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];
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,9 +132,9 @@ 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, // 后续考虑重新启动遥控器对应串口的传输
.owner_id = NULL, // 只有1个遥控器,不需要owner_id
.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键是否按下,后续改为位域后删除
@@ -105,11 +108,10 @@ typedef struct
uint8_t press_l;
uint8_t press_r;
} mouse;
Key_t key[3]; // 改为位域后的键盘索引,空间减少8倍,速度增加16~倍
uint16_t key_temp;
uint8_t key[4][16]; // 当前使用的键盘索引
// Key_t key_test[4]; // 改为位域后的键盘索引,空间减少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]);