From 12796f8e7012460cb1ed6c57cf1920a1139b26c0 Mon Sep 17 00:00:00 2001 From: NeoZng Date: Thu, 8 Jun 2023 15:53:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BA=86=E9=81=A5?= =?UTF-8?q?=E6=8E=A7=E5=99=A8=E8=A7=A3=E6=9E=90=E7=9A=84=E5=8F=AF=E8=AF=BB?= =?UTF-8?q?=E6=80=A7=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=BA=86dji=E7=94=B5?= =?UTF-8?q?=E6=9C=BA=E5=AE=88=E6=8A=A4=E7=BA=BF=E7=A8=8B=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=A3=81=E5=88=A4=E7=B3=BB=E7=BB=9F=E6=9C=AA=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=AF=BC=E8=87=B4=E4=BB=BB=E5=8A=A1=E8=B6=85?= =?UTF-8?q?=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +-- application/application.md | 11 +---- application/cmd/robot_cmd.c | 6 +-- application/cmd/robot_cmd.h | 6 +-- application/shoot/shoot.c | 4 +- bsp/iic/bsp_iic.c | 2 +- bsp/iic/bsp_iic.md | 11 +++-- bsp/spi/bsp_spi.c | 4 +- bsp/spi/bsp_spi.h | 5 +-- modules/motor/DJImotor/dji_motor.c | 22 +++++++-- modules/motor/DJImotor/dji_motor.h | 6 ++- modules/referee/referee_task.c | 12 +++-- modules/referee/rm_referee.h | 12 ++--- modules/remote/remote_control.c | 72 ++++++++++++------------------ modules/remote/remote_control.h | 51 +++++++++++---------- 必须做&禁止做.md | 6 +-- 16 files changed, 119 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index d7d2a13..3d92b78 100644 --- a/README.md +++ b/README.md @@ -172,11 +172,11 @@ Module层主要存放的是类型定义和实例指针数组,在该层没有 - 功能:实现机器人的控制,对机器人**控制**结构进行抽象。 - 在完成BSP层和Module层后,如果在APP层没有控制代码,则代码并无实际功能。换言之,BSP层与Module层的存在是为了APP层更简单、更合理、更易于扩展和移植。本框架的初始目标即是实现:在APP层仅需思考逻辑并用无关硬件的C语言代码实现即可完成整个机器人的控制。所有需要使用的模块和算法都在Module层提供,硬件的抽象在bsp层完成。**所有使用到的模块都在APP层初始化**,因此不需要module自行初始化。 + 在完成BSP层和Module层后,如果在APP层没有控制代码,则代码并无实际功能。换言之,BSP层与Module层的存在是为了APP层更简单、更合理、更易于扩展和移植。本框架的初始目标即是实现:在APP层仅需思考逻辑并用无关硬件的C语言代码实现即可完成整个机器人的控制。所有需要使用的模块和算法都在Module层提供,开发板外设硬件的抽象在bsp层完成。**所有使用到的模块都在APP层初始化**,因此不需要module自行初始化。 -- APP层按照机械设计结构(如云台、发射、底盘)建立对应的子文件夹,在其中完成初始化和相关逻辑功能的编写。还有用于发布指令的云台指令应用和底盘指令应用,前者应该包含一个遥控器模块和一个视觉通信模块,后者包含裁判系统模块。它们包含的模块都会处理一些指令和控制信息,因此将这两个应用从云台和底盘应用中隔离出来。这样还可以方便兼容双板。 +- APP层按照机械设计结构(如云台、发射、底盘、夹爪、抬升、机械臂)建立对应的子文件夹,在其中完成初始化和相关逻辑功能的编写。还有用于发布指令的云台指令应用和底盘指令应用,前者应该包含一个遥控器模块和一个视觉通信模块,后者包含裁判系统模块。它们包含的模块都会处理一些指令和控制信息,这样还可以方便兼容双板。 -- 单双板切换在application的`robot_def.h`中进行,**修改宏定义可以切换开发板的设定模式**。当设定为单板的时候,在`robot.c`中会对gimbal,chassis,shoot,gimbal_cmd,chassis_cmd五个应用都进行初始化。对于双板的情况,需要将上板配置为gimbal board,下板配置为chassis board,它们会分别初始化gimbal/shoot/gimbal_cmd和chassis/chassis_cmd。 +- 单双板切换在application的`robot_def.h`中进行,**修改宏定义可以切换开发板的设定模式**。当设定为单板的时候,在`robot.c`中会对gimbal,chassis,shoot,robot_cmd四个应用都进行初始化。对于双板的情况,需要将上板配置为gimbal board,下板配置为chassis board,它们会分别初始化gimbal/shoot/robot_cmd和chassis - 对于单板的情况,所有应用之间的信息交互通过message center完成。而使用双板时,需要通过板间通信传递控制信息(默认遥控器接收机和pc在云台板,裁判系统在底盘板,因此需要互发信息)。当前通过**条件编译**来控制信息的去向(发往message center/接收,还是通过can comm发送/接收),后续考虑将双板通信纳入message center的实现中,根据`robot_def.h`的开发板定义自动处理通信,降低应用层级的逻辑复杂度。 diff --git a/application/application.md b/application/application.md index e9fac02..cc69d22 100644 --- a/application/application.md +++ b/application/application.md @@ -4,7 +4,7 @@ 这是application层的说明。 - +> todo: 是否有必要将所有电机等模块的初始化参数放到一个头文件? ## 使用说明 @@ -47,15 +47,6 @@ gimbal/chassis/shoot则根据订阅的robot_cmd发布的消息,将具体的控 每个应用的具体流程和实现,参见它们各自的说明文档。 - - ## 开发要点 各个应用之间务必通过`message_center`以发布-订阅的方式进行消息交换,不要出现包含关系,这可以大大减小耦合度并提高合作开发的效率。 - - - - - - - diff --git a/application/cmd/robot_cmd.c b/application/cmd/robot_cmd.c index 30081e6..7f2edb7 100644 --- a/application/cmd/robot_cmd.c +++ b/application/cmd/robot_cmd.c @@ -13,7 +13,7 @@ #define YAW_ALIGN_ANGLE (YAW_CHASSIS_ALIGN_ECD * ECD_ANGLE_COEF_DJI) // 对齐时的角度,0-360 #define PTICH_HORIZON_ANGLE (PITCH_HORIZON_ECD * ECD_ANGLE_COEF_DJI) // pitch水平时电机的角度,0-360 -/* gimbal_cmd应用包含的模块实例指针和交互信息存储*/ +/* cmd应用包含的模块实例指针和交互信息存储*/ #ifdef GIMBAL_BOARD // 对双板的兼容,条件编译 #include "can_comm.h" static CANCommInstance *cmd_can_comm; // 双板通信 @@ -236,7 +236,7 @@ static void MouseKeySet() chassis_cmd_send.chassis_speed_buff = 100; break; } - switch (rc_data[TEMP].key[KEY_PRESS].shift) //待添加 按shift允许超功率 消耗缓冲能量 + switch (rc_data[TEMP].key[KEY_PRESS].shift) // 待添加 按shift允许超功率 消耗缓冲能量 { case 1: @@ -246,7 +246,6 @@ static void MouseKeySet() break; } - } /** @@ -274,7 +273,6 @@ static void EmergencyHandler() robot_state = ROBOT_READY; shoot_cmd_send.shoot_mode = SHOOT_ON; } - } /* 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率) */ diff --git a/application/cmd/robot_cmd.h b/application/cmd/robot_cmd.h index ba9f2e4..f42e9ce 100644 --- a/application/cmd/robot_cmd.h +++ b/application/cmd/robot_cmd.h @@ -1,5 +1,5 @@ -#ifndef GIMBAL_CMD_H -#define GIMBAL_CMD_H +#ifndef ROBOT_CMD_H +#define ROBOT_CMD_H /** @@ -14,4 +14,4 @@ void RobotCMDInit(); */ void RobotCMDTask(); -#endif // !GIMBAL_CMD_H \ No newline at end of file +#endif // !ROBOT_CMD_H \ No newline at end of file diff --git a/application/shoot/shoot.c b/application/shoot/shoot.c index c11637f..e19af5c 100644 --- a/application/shoot/shoot.c +++ b/application/shoot/shoot.c @@ -11,9 +11,9 @@ static DJIMotorInstance *friction_l, *friction_r, *loader; // 拨盘电机 // static servo_instance *lid; 需要增加弹舱盖 static Publisher_t *shoot_pub; -static Shoot_Ctrl_Cmd_s shoot_cmd_recv; // 来自gimbal_cmd的发射控制信息 +static Shoot_Ctrl_Cmd_s shoot_cmd_recv; // 来自cmd的发射控制信息 static Subscriber_t *shoot_sub; -static Shoot_Upload_Data_s shoot_feedback_data; // 来自gimbal_cmd的发射控制信息 +static Shoot_Upload_Data_s shoot_feedback_data; // 来自cmd的发射控制信息 // dwt定时,计算冷却用 static float hibernate_time = 0, dead_time = 0; diff --git a/bsp/iic/bsp_iic.c b/bsp/iic/bsp_iic.c index c11580e..26c34f5 100644 --- a/bsp/iic/bsp_iic.c +++ b/bsp/iic/bsp_iic.c @@ -15,7 +15,7 @@ IICInstance *IICRegister(IIC_Init_Config_s *conf) instance = (IICInstance *)malloc(sizeof(IICInstance)); memset(instance, 0, sizeof(IICInstance)); - instance->dev_address = conf->dev_address << 1; + instance->dev_address = conf->dev_address << 1; // 地址左移一位,最低位为读写位 instance->callback = conf->callback; instance->work_mode = conf->work_mode; instance->handle = conf->handle; diff --git a/bsp/iic/bsp_iic.md b/bsp/iic/bsp_iic.md index d989d02..57a7dcd 100644 --- a/bsp/iic/bsp_iic.md +++ b/bsp/iic/bsp_iic.md @@ -2,13 +2,18 @@ > 预计增加模拟iic +## 注意事项 + +使用时写入地址,不需要左移!!! + +cubemx未配置dma时请勿使用dma传输,其行为是未定义的。 + +## 总线机制详解 + 关于I2C的序列传输,Restart condition和总线仲裁,请看: https://blog.csdn.net/NeoZng/article/details/128496694 https://blog.csdn.net/NeoZng/article/details/128486366 - 使用序列通信则在单次通信后不会释放总线,继续占用直到调用传输函数时传入`IIC_RELEASE`参数. 这个功能只在一条总线上挂载多个主机的时候有用. - -cubemx未配置dma时请勿使用dma传输,其行为是未定义的。 \ No newline at end of file diff --git a/bsp/spi/bsp_spi.c b/bsp/spi/bsp_spi.c index 463879f..c607f26 100644 --- a/bsp/spi/bsp_spi.c +++ b/bsp/spi/bsp_spi.c @@ -130,11 +130,9 @@ void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { // 先拉高片选,结束传输,在判断是否有回调函数,如果有则调用回调函数 HAL_GPIO_WritePin(spi_instance[i]->GPIOx, spi_instance[i]->cs_pin, GPIO_PIN_SET); + // @todo 后续添加holdon模式,由用户自行决定何时释放片选,允许进行连续传输 if (spi_instance[i]->callback != NULL) // 回调函数不为空, 则调用回调函数 - { - spi_instance[i]->callback(spi_instance[i]); - } return; } } diff --git a/bsp/spi/bsp_spi.h b/bsp/spi/bsp_spi.h index 96af1c2..75e6b61 100644 --- a/bsp/spi/bsp_spi.h +++ b/bsp/spi/bsp_spi.h @@ -91,8 +91,7 @@ void SPITransRecv(SPIInstance *spi_ins, uint8_t *ptr_data_rx, uint8_t *ptr_data_ * * @param spi_ins spi实例指针 * @param spi_mode 工作模式,包括阻塞模式(block),中断模式(IT),DMA模式.详见SPI_TXRX_MODE_e的定义 - * @param force_set_flag 强制设置标志,当该标志为1时,强制停止当前spi的收发,并切换到新的工作模式; - * 当该标志为0时,如果当前spi正在收发,则不会切换工作模式,等待传输完成后切换. - * @todo HAL已经提供了防止重入的机制,因此强制设置标志可以去掉,也不需要再判断spi是否正在收发 + * + * @todo 是否直接将mode作为transmit/recv的参数,而不是作为spi实例的属性?两者各有优劣 */ void SPISetMode(SPIInstance *spi_ins, SPI_TXRX_MODE_e spi_mode); diff --git a/modules/motor/DJImotor/dji_motor.c b/modules/motor/DJImotor/dji_motor.c index 0100ff9..1f88cc6 100644 --- a/modules/motor/DJImotor/dji_motor.c +++ b/modules/motor/DJImotor/dji_motor.c @@ -1,5 +1,6 @@ #include "dji_motor.h" #include "general_def.h" +#include "bsp_dwt.h" #include "bsp_log.h" static uint8_t idx = 0; // register idx,是该文件的全局电机索引,在注册时使用 @@ -29,15 +30,12 @@ static CANInstance sender_assignment[6] = { /** * @brief 6个用于确认是否有电机注册到sender_assignment中的标志位,防止发送空帧,此变量将在DJIMotorControl()使用 * flag的初始化在 MotorSenderGrouping()中进行 - * */ static uint8_t sender_enable_flag[6] = {0}; /** * @brief 根据电调/拨码开关上的ID,根据说明书的默认id分配方式计算发送ID和接收ID, * 并对电机进行分组以便处理多电机控制命令 - * - * @param config */ static void MotorSenderGrouping(DJIMotorInstance *motor, CAN_Init_Config_s *config) { @@ -124,7 +122,11 @@ static void DecodeDJIMotor(CANInstance *_instance) // 这里对can instance的id进行了强制转换,从而获得电机的instance实例地址 // _instance指针指向的id是对应电机instance的地址,通过强制转换为电机instance的指针,再通过->运算符访问电机的成员motor_measure,最后取地址获得指针 uint8_t *rxbuff = _instance->rx_buff; - DJI_Motor_Measure_s *measure = &(((DJIMotorInstance *)_instance->id)->measure); // measure要多次使用,保存指针减小访存开销 + DJIMotorInstance *motor = (DJIMotorInstance *)_instance->id; + DJI_Motor_Measure_s *measure = &motor->measure; // measure要多次使用,保存指针减小访存开销 + + DaemonReload(motor->daemon); + motor->dt = DWT_GetDeltaT(&motor->feed_cnt); // 解析数据并对电流和速度进行滤波,电机的反馈报文具体格式见电机说明手册 measure->last_ecd = measure->ecd; @@ -144,6 +146,10 @@ static void DecodeDJIMotor(CANInstance *_instance) measure->total_angle = measure->total_round * 360 + measure->angle_single_round; } +static void DJIMotorLostCallback(void *motor_ptr) +{ +} + // 电机初始化,返回一个电机实例 DJIMotorInstance *DJIMotorInit(Motor_Init_Config_s *config) { @@ -172,6 +178,14 @@ DJIMotorInstance *DJIMotorInit(Motor_Init_Config_s *config) config->can_init_config.id = instance; // set id,eq to address(it is identity) instance->motor_can_instance = CANRegister(&config->can_init_config); + // 注册守护线程 + Daemon_Init_Config_s daemon_config = { + .callback = DJIMotorLostCallback, + .owner_id = instance, + .reload_count = 1, // 10ms未收到数据则丢失 + }; + instance->daemon = DaemonRegister(&daemon_config); + DJIMotorEnable(instance); dji_motor_instance[idx++] = instance; return instance; diff --git a/modules/motor/DJImotor/dji_motor.h b/modules/motor/DJImotor/dji_motor.h index d83ba95..bd07c1a 100644 --- a/modules/motor/DJImotor/dji_motor.h +++ b/modules/motor/DJImotor/dji_motor.h @@ -19,6 +19,7 @@ #include "controller.h" #include "motor_def.h" #include "stdint.h" +#include "daemon.h" #define DJI_MOTOR_CNT 12 @@ -47,7 +48,6 @@ typedef struct */ typedef struct { - DJI_Motor_Measure_s measure; // 电机测量值 Motor_Control_Setting_s motor_settings; // 电机设置 Motor_Controller_s motor_controller; // 电机控制器 @@ -59,6 +59,10 @@ typedef struct Motor_Type_e motor_type; // 电机类型 Motor_Working_Type_e stop_flag; // 启停标志 + + DaemonInstance* daemon; + uint32_t feed_cnt; + float dt; } DJIMotorInstance; /** diff --git a/modules/referee/referee_task.c b/modules/referee/referee_task.c index f307622..4cfda1e 100644 --- a/modules/referee/referee_task.c +++ b/modules/referee/referee_task.c @@ -13,10 +13,12 @@ #include "rm_referee.h" #include "referee_UI.h" #include "string.h" +#include "cmsis_os.h" static Referee_Interactive_info_t *Interactive_data; // UI绘制需要的机器人状态数据 static referee_info_t *referee_recv_info; // 接收到的裁判系统数据 -uint8_t UI_Seq; // 包序号,供整个referee文件使用 +uint8_t UI_Seq; // 包序号,供整个referee文件使用 +// @todo 不应该使用全局变量 /** * @brief 判断各种ID,选择客户端ID @@ -43,6 +45,7 @@ referee_info_t *Referee_Interactive_init(UART_HandleTypeDef *referee_usart_handl { referee_recv_info = RefereeInit(referee_usart_handle); // 初始化裁判系统的串口,并返回裁判系统反馈数据指针 Interactive_data = UI_data; // 获取UI绘制需要的机器人状态数据 + referee_recv_info->init_flag = 1; return referee_recv_info; } @@ -60,9 +63,12 @@ static uint32_t shoot_line_location[10] = {540, 960, 490, 515, 565}; void My_UI_init() { + if (!referee_recv_info->init_flag) + vTaskDelete(NULL); // 如果没有初始化裁判系统则直接删除ui任务 while (referee_recv_info->GameRobotState.robot_id == 0) - ; - DeterminRobotID(); + osDelay(100); // 若还未收到裁判系统数据,等待一段时间后再检查 + + DeterminRobotID(); // 确定ui要发送到的目标客户端 UIDelete(&referee_recv_info->referee_id, UI_Data_Del_ALL, 0); // 清空UI // 绘制发射基准线 diff --git a/modules/referee/rm_referee.h b/modules/referee/rm_referee.h index 1cf83b2..e935e94 100644 --- a/modules/referee/rm_referee.h +++ b/modules/referee/rm_referee.h @@ -41,6 +41,8 @@ typedef struct // 自定义交互数据的接收 Communicate_ReceiveData_t ReceiveData; + uint8_t init_flag; + } referee_info_t; // 模式是否切换标志位,0为未切换,1为切换,static定义默认为0 @@ -67,11 +69,11 @@ typedef struct Chassis_Power_Data_s Chassis_Power_Data; // 功率控制 // 上一次的模式,用于flag判断 - chassis_mode_e chassis_last_mode; - gimbal_mode_e gimbal_last_mode; - shoot_mode_e shoot_last_mode; - friction_mode_e friction_last_mode; - lid_mode_e lid_last_mode; + chassis_mode_e chassis_last_mode; + gimbal_mode_e gimbal_last_mode; + shoot_mode_e shoot_last_mode; + friction_mode_e friction_last_mode; + lid_mode_e lid_last_mode; Chassis_Power_Data_s Chassis_last_Power_Data; } Referee_Interactive_info_t; diff --git a/modules/remote/remote_control.c b/modules/remote/remote_control.c index b03efa5..239403d 100644 --- a/modules/remote/remote_control.c +++ b/modules/remote/remote_control.c @@ -5,8 +5,8 @@ #include "stdlib.h" #include "daemon.h" -#define RC_MOUSE_SMOOTH_COEF 0.9 // 鼠标平滑滤波器的阶数 #define REMOTE_CONTROL_FRAME_SIZE 18u // 遥控器接收的buffer大小 + // 遥控器数据 static RC_ctrl_t rc_ctrl[2]; //[0]:当前数据TEMP,[1]:上一次的数据LAST.用于按键持续按下和切换的判断 static uint8_t rc_init_flag = 0; // 遥控器初始化标志位 @@ -14,6 +14,7 @@ static uint8_t rc_init_flag = 0; // 遥控器初始化标志位 // 遥控器拥有的串口实例,因为遥控器是单例,所以这里只有一个,就不封装了 static USARTInstance *rc_usart_instance; static DaemonInstance *rc_daemon_instance; + /** * @brief 矫正遥控器摇杆的值,超过660或者小于-660的值都认为是无效值,置0 * @@ -21,22 +22,17 @@ static DaemonInstance *rc_daemon_instance; static void RectifyRCjoystick() { for (uint8_t i = 0; i < 5; ++i) - { if (abs(*(&rc_ctrl[TEMP].rc.rocker_l_ + i)) > 660) *(&rc_ctrl[TEMP].rc.rocker_l_ + i) = 0; - } } /** - * @brief remote control protocol resolution - * @param[in] sbus_buf: raw data point - * @param[out] rc_ctrl: remote control data struct point - * @retval none + * @brief 遥控器数据解析 + * + * @param sbus_buf 接收buffer */ - static void sbus_to_rc(const uint8_t *sbus_buf) { - // 摇杆,直接解算时减去偏置 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 @@ -49,56 +45,45 @@ static void sbus_to_rc(const uint8_t *sbus_buf) rc_ctrl[TEMP].rc.switch_left = ((sbus_buf[5] >> 4) & 0x000C) >> 2; //!< Switch left // 鼠标解析 - rc_ctrl[TEMP].mouse.x = (float)(sbus_buf[6] | (sbus_buf[7] << 8))*RC_MOUSE_SMOOTH_COEF + (1-RC_MOUSE_SMOOTH_COEF)*(float)(rc_ctrl[LAST].mouse.x); //!< Mouse X axis - rc_ctrl[TEMP].mouse.y = (float)(sbus_buf[8] | (sbus_buf[9] << 8))*RC_MOUSE_SMOOTH_COEF + (1-RC_MOUSE_SMOOTH_COEF)*(float)(rc_ctrl[TEMP].mouse.y); //!< Mouse Y axis + rc_ctrl[TEMP].mouse.x = (sbus_buf[6] | (sbus_buf[7] << 8)); //!< Mouse X axis + rc_ctrl[TEMP].mouse.y = (sbus_buf[8] | (sbus_buf[9] << 8)); //!< Mouse Y axis 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 ? - // 位域的按键值解算,直接memcpy即可,注意小端低字节在前,即lsb在第一位,msb在最后. 尚未测试 + // 位域的按键值解算,直接memcpy即可,注意小端低字节在前,即lsb在第一位,msb在最后 *(uint16_t *)&rc_ctrl[TEMP].key[KEY_PRESS] = (uint16_t)(sbus_buf[14] | (sbus_buf[15] << 8)); - - if (rc_ctrl[TEMP].key[KEY_PRESS].ctrl) + if (rc_ctrl[TEMP].key[KEY_PRESS].ctrl) // 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) + if (rc_ctrl[TEMP].key[KEY_PRESS].shift) // 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++) + uint16_t key_now = rc_ctrl[TEMP].key[KEY_PRESS].keys, // 当前按键是否按下 + key_last = rc_ctrl[LAST].key[KEY_PRESS].keys, // 上一次按键是否按下 + key_with_ctrl = rc_ctrl[TEMP].key[KEY_PRESS_WITH_CTRL].keys, // 当前ctrl组合键是否按下 + key_with_shift = rc_ctrl[TEMP].key[KEY_PRESS_WITH_SHIFT].keys, // 当前shift组合键是否按下 + key_last_with_ctrl = rc_ctrl[LAST].key[KEY_PRESS_WITH_CTRL].keys, // 上一次ctrl组合键是否按下 + key_last_with_shift = rc_ctrl[LAST].key[KEY_PRESS_WITH_SHIFT].keys; // 上一次shift组合键是否按下 + + for (uint16_t i = 0, j = 0x1; i < 16; j <<= 1, 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)) - { + if (i == 4 || i == 5) // 4,5位为ctrl和shift,直接跳过 + continue; + // 如果当前按键按下,上一次按键没有按下,且ctrl和shift组合键没有按下,则按键按下计数加1(检测到上升沿) + if ((key_now & j) && !(key_last & j) && !(key_with_ctrl & j) && !(key_with_shift & 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)) - { + // 当前ctrl组合键按下,上一次ctrl组合键没有按下,则ctrl组合键按下计数加1(检测到上升沿) + if ((key_with_ctrl & j) && !(key_last_with_ctrl & j)) 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)) - { + // 当前shift组合键按下,上一次shift组合键没有按下,则shift组合键按下计数加1(检测到上升沿) + if ((key_with_shift & j) && !(key_last_with_shift & j)) 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)); // 保存上一次的数据,用于按键持续按下和切换的判断 + memcpy(&rc_ctrl[LAST], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断 } /** @@ -117,6 +102,7 @@ static void RemoteControlRxCallback() */ static void RCLostCallback(void *id) { + memset(rc_ctrl, 0, sizeof(rc_ctrl)); // 清空遥控器数据 USARTServiceInit(rc_usart_instance); // 尝试重新启动接收 } @@ -137,7 +123,7 @@ RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle) rc_daemon_instance = DaemonRegister(&daemon_conf); rc_init_flag = 1; - return (RC_ctrl_t *)&rc_ctrl; + return rc_ctrl; } uint8_t RemoteControlIsOnline() diff --git a/modules/remote/remote_control.h b/modules/remote/remote_control.h index 8407ddd..c12f810 100644 --- a/modules/remote/remote_control.h +++ b/modules/remote/remote_control.h @@ -40,11 +40,6 @@ #define switch_is_down(s) (s == RC_SW_DOWN) #define switch_is_mid(s) (s == RC_SW_MID) #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键是否按下,后续改为位域后删除 @@ -67,26 +62,31 @@ /* ----------------------- Data Struct ------------------------------------- */ // 待测试的位域结构体,可以极大提升解析速度 -typedef struct +typedef union { - uint16_t w : 1; - uint16_t s : 1; - uint16_t d : 1; - uint16_t a : 1; - uint16_t shift : 1; - uint16_t ctrl : 1; - uint16_t q : 1; - uint16_t e : 1; - uint16_t r : 1; - uint16_t f : 1; - uint16_t g : 1; - uint16_t z : 1; - uint16_t x : 1; - uint16_t c : 1; - uint16_t v : 1; - uint16_t b : 1; + struct // 用于访问键盘状态 + { + uint16_t w : 1; + uint16_t s : 1; + uint16_t d : 1; + uint16_t a : 1; + uint16_t shift : 1; + uint16_t ctrl : 1; + uint16_t q : 1; + uint16_t e : 1; + uint16_t r : 1; + uint16_t f : 1; + uint16_t g : 1; + uint16_t z : 1; + uint16_t x : 1; + uint16_t c : 1; + uint16_t v : 1; + uint16_t b : 1; + }; + uint16_t keys; // 用于memcpy而不需要进行强制类型转换 } Key_t; +// @todo 当前结构体嵌套过深,需要进行优化 typedef struct { struct @@ -104,12 +104,11 @@ typedef struct { int16_t x; int16_t y; - int16_t z; uint8_t press_l; uint8_t press_r; } mouse; - - Key_t key[3]; // 改为位域后的键盘索引,空间减少8倍,速度增加16~倍 + + Key_t key[3]; // 改为位域后的键盘索引,空间减少8倍,速度增加16~倍 uint8_t key_count[3][16]; } RC_ctrl_t; @@ -126,7 +125,7 @@ RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle); /** * @brief 检查遥控器是否在线,若尚未初始化也视为离线 - * + * * @return uint8_t 1:在线 0:离线 */ uint8_t RemoteControlIsOnline(); diff --git a/必须做&禁止做.md b/必须做&禁止做.md index e3a9d0b..40ffdff 100644 --- a/必须做&禁止做.md +++ b/必须做&禁止做.md @@ -2,7 +2,8 @@ ## 禁止在临界区使用延时,这会导致因中断关闭使得定时器无法进入中断更新时间,进而卡死系统 -除非你使用的是基于计数寄存器差值的延时方法,或阻塞式的for延时 +除非你使用的是基于计数寄存器差值的延时方法,或阻塞式的for延时。 +**若有必要,应该使用`bsp_dwt.h`提供的接口。 ## 禁止摸鱼 @@ -14,7 +15,6 @@ ## 请给你编写的bsp和module提供详细的文档和使用示例,并为接口增加安全检查 -用于调试的条件编译和log输出也是必须的。 +用于调试的条件编译和(若有可能)log输出也是必须的。 另外,“treat your user as idot!” -