diff --git a/Core/Src/freertos.c b/Core/Src/freertos.c index 5745da1..474b313 100644 --- a/Core/Src/freertos.c +++ b/Core/Src/freertos.c @@ -121,14 +121,14 @@ const osThreadAttr_t WS2812Task_attributes = { /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ -//Todo: 测试instask +//@Todo:测试instask const osThreadAttr_t instask_attributes = { - .name = "instask", - .priority = osPriorityAboveNormal, // 较高优先级 - .stack_size = 1024 * 4 // 栈大小,单位是字节(通常是字数的4倍) + .name = "instask", + .priority = osPriorityAboveNormal, // 较高优先级 + .stack_size = 1024 * 4 // 栈大小,单位是字节(通常是字数的4倍) }; -//Todo:测试使用 +//@Todo:测试使用 osThreadId insTaskHandle; void StartINSTASK(void const *argument); @@ -136,28 +136,38 @@ void StartINSTASK(void const *argument); /* USER CODE END FunctionPrototypes */ void StartDefaultTask(void *argument); + void ShootTask(void *argument); + void GimbalTask(void *argument); + void ChassisTask(void *argument); + void StartInitTask(void *argument); + void VisionTask(void *argument); + void CmdTask(void *argument); + void RefereeTask(void *argument); + extern void ws2812Task(void *argument); extern void MX_USB_DEVICE_Init(void); + void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ /* Hook prototypes */ void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName); + void vApplicationMallocFailedHook(void); /* USER CODE BEGIN 4 */ void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName) { - /* Run time stack overflow checking is performed if - configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is - called if a stack overflow is detected. */ + /* Run time stack overflow checking is performed if + configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is + called if a stack overflow is detected. */ } /* USER CODE END 4 */ @@ -165,16 +175,16 @@ void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName) /* USER CODE BEGIN 5 */ void vApplicationMallocFailedHook(void) { - /* vApplicationMallocFailedHook() will only be called if - configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h. It is a hook - function that will get called if a call to pvPortMalloc() fails. - pvPortMalloc() is called internally by the kernel whenever a task, queue, - timer or semaphore is created. It is also called by various parts of the - demo application. If heap_1.c or heap_2.c are used, then the size of the - heap available to pvPortMalloc() is defined by configTOTAL_HEAP_SIZE in - FreeRTOSConfig.h, and the xPortGetFreeHeapSize() API function can be used - to query the size of free heap space that remains (although it does not - provide information on how the remaining heap might be fragmented). */ + /* vApplicationMallocFailedHook() will only be called if + configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h. It is a hook + function that will get called if a call to pvPortMalloc() fails. + pvPortMalloc() is called internally by the kernel whenever a task, queue, + timer or semaphore is created. It is also called by various parts of the + demo application. If heap_1.c or heap_2.c are used, then the size of the + heap available to pvPortMalloc() is defined by configTOTAL_HEAP_SIZE in + FreeRTOSConfig.h, and the xPortGetFreeHeapSize() API function can be used + to query the size of free heap space that remains (although it does not + provide information on how the remaining heap might be fragmented). */ } /* USER CODE END 5 */ @@ -184,25 +194,26 @@ void vApplicationMallocFailedHook(void) * @param None * @retval None */ -void MX_FREERTOS_Init(void) { +void MX_FREERTOS_Init(void) +{ /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* USER CODE BEGIN RTOS_MUTEX */ - /* add mutexes, ... */ + /* add mutexes, ... */ /* USER CODE END RTOS_MUTEX */ /* USER CODE BEGIN RTOS_SEMAPHORES */ - /* add semaphores, ... */ + /* add semaphores, ... */ /* USER CODE END RTOS_SEMAPHORES */ /* USER CODE BEGIN RTOS_TIMERS */ - /* start timers, add new ones, ... */ + /* start timers, add new ones, ... */ /* USER CODE END RTOS_TIMERS */ /* USER CODE BEGIN RTOS_QUEUES */ - /* add queues, ... */ + /* add queues, ... */ /* USER CODE END RTOS_QUEUES */ /* Create the thread(s) */ @@ -234,16 +245,15 @@ void MX_FREERTOS_Init(void) { WS2812TaskHandle = osThreadNew(ws2812Task, NULL, &WS2812Task_attributes); /* USER CODE BEGIN RTOS_THREADS */ - /* add threads, ... */ - //Todo:INS_Task是测试版本 - // 创建线程 - insTaskHandle = osThreadNew(StartINSTASK, NULL, &instask_attributes); + /* add threads, ... */ + //@Todo:INS_Task是测试版本 + // 创建线程 + insTaskHandle = osThreadNew(StartINSTASK, NULL, &instask_attributes); /* USER CODE END RTOS_THREADS */ /* USER CODE BEGIN RTOS_EVENTS */ - /* add events, ... */ + /* add events, ... */ /* USER CODE END RTOS_EVENTS */ - } /* USER CODE BEGIN Header_StartDefaultTask */ @@ -258,11 +268,11 @@ __weak void StartDefaultTask(void *argument) /* init code for USB_DEVICE */ MX_USB_DEVICE_Init(); /* USER CODE BEGIN StartDefaultTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END StartDefaultTask */ } @@ -276,11 +286,11 @@ __weak void StartDefaultTask(void *argument) __weak void ShootTask(void *argument) { /* USER CODE BEGIN ShootTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END ShootTask */ } @@ -294,11 +304,11 @@ __weak void ShootTask(void *argument) __weak void GimbalTask(void *argument) { /* USER CODE BEGIN GimbalTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END GimbalTask */ } @@ -312,11 +322,11 @@ __weak void GimbalTask(void *argument) __weak void ChassisTask(void *argument) { /* USER CODE BEGIN ChassisTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END ChassisTask */ } @@ -330,11 +340,11 @@ __weak void ChassisTask(void *argument) __weak void StartInitTask(void *argument) { /* USER CODE BEGIN StartInitTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END StartInitTask */ } @@ -348,11 +358,11 @@ __weak void StartInitTask(void *argument) __weak void VisionTask(void *argument) { /* USER CODE BEGIN VisionTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END VisionTask */ } @@ -366,11 +376,11 @@ __weak void VisionTask(void *argument) __weak void CmdTask(void *argument) { /* USER CODE BEGIN CmdTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END CmdTask */ } @@ -384,36 +394,38 @@ __weak void CmdTask(void *argument) __weak void RefereeTask(void *argument) { /* USER CODE BEGIN RefereeTask */ - /* Infinite loop */ - for (;;) - { - osDelay(1); - } + /* Infinite loop */ + for (;;) + { + osDelay(1); + } /* USER CODE END RefereeTask */ } /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ -//Todo:INS_Task是测试阶段使用。 +//@Todo:INS_Task是测试阶段使用。 __attribute__((noreturn)) void StartINSTASK(void const *argument) { - static float ins_start; - static float ins_dt; - INS_Init(); // 确保BMI088被正确初始化. - // LOGINFO("[freeRTOS] INS Task Start"); - for (;;) - { - // 1kHz - // ins_start = DWT_GetTimeline_ms(); - INS_Task(); - // ins_dt = DWT_GetTimeline_ms() - ins_start; - // if (ins_dt > 1) - // Todo: LOGERROR("[freeRTOS] INS Task is being DELAY! dt = [%f]", &ins_dt); - // // VisionSend(); // 解算完成后发送视觉数据,但是当前的实现不太优雅,后续若添加硬件触发需要重新考虑结构的组织 - osDelay(1); // 1ms, 1kHz - } + static float ins_start; + static float ins_dt; + INS_Init(); // 确保BMI088被正确初始化. + // LOGINFO("[freeRTOS] INS Task Start"); + for (;;) + { + // 1kHz + // ins_start = DWT_GetTimeline_ms(); + INS_Task(); + // ins_dt = DWT_GetTimeline_ms() - ins_start; + // if (ins_dt > 1) + // @Todo: LOGERROR("[freeRTOS] INS Task is being DELAY! dt = [%f]", &ins_dt); + // // VisionSend(); // 解算完成后发送视觉数据,但是当前的实现不太优雅,后续若添加硬件触发需要重新考虑结构的组织 + osDelay(1); // 1ms, 1kHz + } } + + /* USER CODE END Application */ diff --git a/User_Code/application/indicator_app/ws2812status.c b/User_Code/application/indicator_app/ws2812status.c index 7bde183..a5cfaf0 100644 --- a/User_Code/application/indicator_app/ws2812status.c +++ b/User_Code/application/indicator_app/ws2812status.c @@ -53,6 +53,7 @@ void robotSelfCheck(void) void ws2812Task(void *argument) { (void) argument; + RobotMode_t RobotMode = REMOTE_NOT_CONNECTED; // 初始状态为遥控器未连接 while (1) { switch (RobotMode) diff --git a/User_Code/bsp/usart/bsp_usart.c b/User_Code/bsp/usart/bsp_usart.c index f773b4a..5c02096 100644 --- a/User_Code/bsp/usart/bsp_usart.c +++ b/User_Code/bsp/usart/bsp_usart.c @@ -17,7 +17,7 @@ /* usart service instance, modules' info would be recoreded here using USARTRegister() */ /* usart服务实例,所有注册了usart的模块信息会被保存在这里 */ static uint8_t idx; -static USARTInstance *usart_instance[DEVICE_USART_CNT] = {NULL}; +static USART_Instance *usart_instance[DEVICE_USART_CNT] = {NULL}; /** * @brief 启动串口服务,会在每个实例注册之后自动启用接收,当前实现为DMA接收,后续可能添加IT和BLOCKING接收 @@ -27,7 +27,7 @@ static USARTInstance *usart_instance[DEVICE_USART_CNT] = {NULL}; * * @param _instance instance owned by module,模块拥有的串口实例 */ -void USARTServiceInit(USARTInstance *_instance) +void USARTServiceInit(USART_Instance *_instance) { HAL_UARTEx_ReceiveToIdle_DMA(_instance->usart_handle, _instance->recv_buff, _instance->recv_buff_size); // 关闭dma half transfer中断防止两次进入HAL_UARTEx_RxEventCallback() @@ -36,7 +36,7 @@ void USARTServiceInit(USARTInstance *_instance) __HAL_DMA_DISABLE_IT(_instance->usart_handle->hdmarx, DMA_IT_HT); } -USARTInstance *USARTRegister(USART_Init_Config_s *init_config) +USART_Instance *USARTRegister(USART_Init_Config_s *init_config) { if (idx >= DEVICE_USART_CNT) // 超过最大实例数 while (1) @@ -47,8 +47,8 @@ USARTInstance *USARTRegister(USART_Init_Config_s *init_config) while (1) LOGERROR("[bsp_usart] USART instance already registered!"); - USARTInstance *instance = (USARTInstance *) malloc(sizeof(USARTInstance)); - memset(instance, 0, sizeof(USARTInstance)); + USART_Instance *instance = (USART_Instance *) malloc(sizeof(USART_Instance)); + memset(instance, 0, sizeof(USART_Instance)); instance->usart_handle = init_config->usart_handle; instance->recv_buff_size = init_config->recv_buff_size; @@ -60,7 +60,7 @@ USARTInstance *USARTRegister(USART_Init_Config_s *init_config) } /* @todo 当前仅进行了形式上的封装,后续要进一步考虑是否将module的行为与bsp完全分离 */ -void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size, USART_TRANSFER_MODE mode) +void USARTSend(USART_Instance *_instance, uint8_t *send_buf, uint16_t send_size, USART_TRANSFER_MODE mode) { switch (mode) { @@ -80,7 +80,7 @@ void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size, } /* 串口发送时,gstate会被设为BUSY_TX */ -uint8_t USARTIsReady(USARTInstance *_instance) +uint8_t USARTIsReady(USART_Instance *_instance) { if (_instance->usart_handle->gState | HAL_UART_STATE_BUSY_TX) return 0; @@ -141,3 +141,5 @@ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) } } } + + diff --git a/User_Code/bsp/usart/bsp_usart.h b/User_Code/bsp/usart/bsp_usart.h index 4aa5d0c..a192809 100644 --- a/User_Code/bsp/usart/bsp_usart.h +++ b/User_Code/bsp/usart/bsp_usart.h @@ -27,7 +27,7 @@ typedef struct uint8_t recv_buff_size; // 模块接收一包数据的大小 UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle usart_module_callback module_callback; // 解析收到的数据的回调函数 -} USARTInstance; +} USART_Instance; /* usart 初始化配置结构体 */ typedef struct @@ -42,14 +42,14 @@ typedef struct * * @param init_config 传入串口初始化结构体 */ -USARTInstance *USARTRegister(USART_Init_Config_s *init_config); +USART_Instance *USARTRegister(USART_Init_Config_s *init_config); /** * @brief 启动串口服务,需要传入一个usart实例.一般用于lost callback的情况(使用串口的模块daemon) * * @param _instance */ -void USARTServiceInit(USARTInstance *_instance); +void USARTServiceInit(USART_Instance *_instance); /** @@ -62,7 +62,7 @@ void USARTServiceInit(USARTInstance *_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, USART_TRANSFER_MODE mode); +void USARTSend(USART_Instance *_instance, uint8_t *send_buf, uint16_t send_size, USART_TRANSFER_MODE mode); /** * @brief 判断串口是否准备好,用于连续或异步的IT/DMA发送 @@ -70,6 +70,6 @@ void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size, * @param _instance 要判断的串口实例 * @return uint8_t ready 1, busy 0 */ -uint8_t USARTIsReady(USARTInstance *_instance); +uint8_t USARTIsReady(USART_Instance *_instance); #endif diff --git a/User_Code/module/paramdef/robot_def.h b/User_Code/module/paramdef/robot_def.h index 995b8b5..5373470 100644 --- a/User_Code/module/paramdef/robot_def.h +++ b/User_Code/module/paramdef/robot_def.h @@ -2,7 +2,8 @@ #ifndef ROBOT_DEF_H #define ROBOT_DEF_H -typedef enum { +typedef enum +{ NORMAL_MODE = 0, // 正常模式,对应数值0 SYS_ERROR_OCCURRED = 1, // 系统错误,对应数值1 REMOTE_NOT_CONNECTED = 2, // 遥控器未连接,对应数值2 @@ -12,8 +13,7 @@ typedef enum { // 添加其他模式 } RobotMode_t; //机器人控制模式 -// 声明并初始化变量 -RobotMode_t RobotMode = REMOTE_NOT_CONNECTED; // 初始状态为遥控器未连接 +extern RobotMode_t RobotMode; // #pragma pack() // 开启字节对齐,结束前面的#pragma pack(1) diff --git a/User_Code/module/periph/remote_control/key_define.h b/User_Code/module/periph/remote_control/key_define.h new file mode 100644 index 0000000..9a5e6a0 --- /dev/null +++ b/User_Code/module/periph/remote_control/key_define.h @@ -0,0 +1,70 @@ +// +// Created by ASUS on 2025/12/14. +// + +#ifndef TRONONEH7_SCAFFOLD_KEY_DEFINE_H +#define TRONONEH7_SCAFFOLD_KEY_DEFINE_H + +#ifndef KEY_DEFINE_H +#define KEY_DEFINE_H + +#include "stdint.h" + +// 用于遥控器数据读取,遥控器数据是一个大小为2的数组 +#define LAST 1 +#define TEMP 0 + +// 获取按键操作 +#define KEY_PRESS 0 +#define KEY_STATE 1 +#define KEY_PRESS_WITH_CTRL 1 +#define KEY_PRESS_WITH_SHIFT 2 + +/* ----------------------- PC Key Definition-------------------------------- */ +// 对应key[x][0~16],获取对应的键;例如通过key[KEY_PRESS][Key_W]获取W键是否按下,后续改为位域后删除 +#define Key_W 0 +#define Key_S 1 +#define Key_A 2 +#define Key_D 3 +#define Key_Shift 4 +#define Key_Ctrl 5 +#define Key_Q 6 +#define Key_E 7 +#define Key_R 8 +#define Key_F 9 +#define Key_G 10 +#define Key_Z 11 +#define Key_X 12 +#define Key_C 13 +#define Key_V 14 +#define Key_B 15 + +/* ----------------------- Data Struct ------------------------------------- */ +// 待测试的位域结构体,可以极大提升解析速度 +typedef union +{ + struct // 用于访问键盘状态 + { + uint16_t w: 1; + uint16_t s: 1; + uint16_t a: 1; + uint16_t d: 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; +#endif // !KEY_DEFINE_H + +#endif //TRONONEH7_SCAFFOLD_KEY_DEFINE_H diff --git a/User_Code/module/periph/remote_control/rc.c b/User_Code/module/periph/remote_control/rc.c index d99842d..3a9ec05 100644 --- a/User_Code/module/periph/remote_control/rc.c +++ b/User_Code/module/periph/remote_control/rc.c @@ -1,5 +1,430 @@ -// -// Created by ASUS on 2025/11/17. -// + +/*************************发射机DT7*************************** + * * + * ----------------------------------------------------- * + * | (上-1) (上-1) | * + * |SW_L|(中-3) SW_R|(中-3) | * + * | (下-2) (下-2) | * + * | | * + * | | ^ | | ^ | | * + * | | 3 |左摇杆 右摇杆| 1 | | * + * | --- --- --- --- | * + * |< 2> < 0>| * + * | --- --- --- --- | * + * | | | | | | * + * | | | | | | * + * | | * + * ----------------------------------------------------- * + * * + **************************遥控器信息************************** + *域 通道0 通道1 通道2 通道3 S1 S2 * + *偏移 0 11 22 33 44 46 * + *长度(bit) 11 11 11 11 2 2 * + *符号位 无 无 无 无 无 无 * + *范围 ***********最大值1684********* *最大值3* * + * * 中间值1024 * *最小值1* * + * ***********最小值364********** * + *功能 1:上 1:上 * + * 2:下 2:下 * + * 3:中 3:中 * + * * + ***************************鼠标信息*************************** + *域 鼠标x轴 鼠标y轴 鼠标z轴 鼠标左键 鼠标右键* + *偏移 48 64 80 86 94 * + *长度 16 16 16 8 8 * + *符号位 有 有 有 无 无 * + *范围 ******最大值32767***** ***最大值1*** * + * * 最小值-32768 * ***最小值0*** * + * ******静止值0********* * + *功能 ***鼠标在XYZ轴的移动速度*** *鼠标左右键是否按下* + * * 负值表示往左移动 * * 0:没按下 * + * ***正值表示往右移动******** *****1:按下********* + * * + * * + ***************************键盘信息*************************** + *域 按键 * + *偏移 102 * + *长度 16 * + *符号位 无 * + *范围 位值标识 * + *功能 每个按键对应一个bit * + * Bit 0:W键 * + * Bit 1:S键 * + * Bit 2:A键 * + * Bit 3:D键 * + * Bit 4:Shift键 * + * Bit 5:Ctrl键 * + * Bit 6:Q键 * + * Bit 7:E键 * + * Bit 8:R键 * + * Bit 9:F键 * + * Bit10:G键 * + * Bit11:Z键 * + * Bit12:X键 * + * Bit13:C键 * + * Bit14:V键 * + * Bit15:B键 * + **************************************************************/ #include "rc.h" +#include "string.h" +#include "bsp_usart.h" +#include "memory.h" +#include "stdlib.h" +#include "daemon.h" +#include "indicator_app/ws2812status.h" +#include "robot_def.h" + +#define REMOTE_CONTROL_FRAME_SIZE 25u // 遥控器接收的buffer大小 + +//@todo:测试define +#define REMOTE_FS_I6X + +// 定义SBUS协议的起始标志 +#define SBUS_HEAD 0X0F + +// 定义SBUS协议的结束标志 +#define SBUS_END 0X00 + +// 遥控器数据 +static RC_ctrl_t rc_ctrl[2]; //[0]:当前数据TEMP,[1]:上一次的数据LAST.用于按键持续按下和切换的判断 +static uint8_t rc_init_flag = 0; // 遥控器初始化标志位 + +// 遥控器拥有的串口实例,因为遥控器是单例,所以这里只有一个,就不封装了 +static USART_Instance *rc_usart_instance; +static Daemon_Instance *rc_daemon_instance; + + +#ifdef REMOTE_DJI_DT7 +/** + * @brief 矫正遥控器摇杆的值,超过660或者小于-660的值都认为是无效值,置0 + * + */ +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 遥控器数据解析 + * + * @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 + rc_ctrl[TEMP].rc.rocker_l_ = (((sbus_buf[2] >> 6) | (sbus_buf[3] << 2) | (sbus_buf[4] << 10)) & 0x07ff) - + RC_CH_VALUE_OFFSET; //!< Channel 2 + rc_ctrl[TEMP].rc.rocker_l1 = (((sbus_buf[4] >> 1) | (sbus_buf[5] << 7)) & 0x07ff) - RC_CH_VALUE_OFFSET; + //!< Channel 3 + rc_ctrl[TEMP].rc.dial = ((sbus_buf[16] | (sbus_buf[17] << 8)) & 0x07FF) - RC_CH_VALUE_OFFSET; // 左侧拨轮 + RectifyRCjoystick(); + // 开关,0左1右 + rc_ctrl[TEMP].rc.switch_right = ((sbus_buf[5] >> 4) & 0x0003); //!< Switch right + rc_ctrl[TEMP].rc.switch_left = ((sbus_buf[5] >> 4) & 0x000C) >> 2; //!< Switch left + + // 鼠标解析 + 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.z = (sbus_buf[10] | (sbus_buf[11] << 8)); //!< Mouse Z 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在最后 + *(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) // 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) // 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)); + + 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 (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]++; + // 当前ctrl组合键按下,上一次ctrl组合键没有按下,则ctrl组合键按下计数加1(检测到上升沿) + if ((key_with_ctrl & j) && !(key_last_with_ctrl & j)) + rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_CTRL][i]++; + // 当前shift组合键按下,上一次shift组合键没有按下,则shift组合键按下计数加1(检测到上升沿) + if ((key_with_shift & j) && !(key_last_with_shift & j)) + rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_SHIFT][i]++; + } + + memcpy(&rc_ctrl[LAST], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断 +} +#endif + +#ifdef REMOTE_FS_I6X +/** + * @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 + // rc_ctrl[TEMP].rc.rocker_l_ = (((sbus_buf[2] >> 6) | (sbus_buf[3] << 2) | (sbus_buf[4] << 10)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 2 + // rc_ctrl[TEMP].rc.rocker_l1 = (((sbus_buf[4] >> 1) | (sbus_buf[5] << 7)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 3 + // rc_ctrl[TEMP].rc.dial = ((sbus_buf[16] | (sbus_buf[17] << 8)) & 0x07FF) - RC_CH_VALUE_OFFSET; // 左侧拨轮 + // RectifyRCjoystick(); + // // 开关,0左1右 + // rc_ctrl[TEMP].rc.switch_right = ((sbus_buf[5] >> 4) & 0x0003); //!< Switch right + // rc_ctrl[TEMP].rc.switch_left = ((sbus_buf[5] >> 4) & 0x000C) >> 2; //!< Switch left + + // // 鼠标解析 + // 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.z = (sbus_buf[10] | (sbus_buf[11] << 8)); //!< Mouse Z 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在最后 + // *(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) // 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) // 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)); + + // 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 (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]++; + // // 当前ctrl组合键按下,上一次ctrl组合键没有按下,则ctrl组合键按下计数加1(检测到上升沿) + // if ((key_with_ctrl & j) && !(key_last_with_ctrl & j)) + // rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_CTRL][i]++; + // // 当前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 ((sbus_buf[0] != SBUS_HEAD) || (sbus_buf[24] != SBUS_END)) + return; + + // if (sbus_buf[23] == 0x0C) + // rc_ctrl->online = 0; + // else + // rc_ctrl->online = 1; + + rc_ctrl[TEMP].rc.ch[0] = ((sbus_buf[1] | sbus_buf[2] << 8) & 0x07FF); // 通道1 (云台yaw) + rc_ctrl[TEMP].rc.ch[1] = ((sbus_buf[2] >> 3 | sbus_buf[3] << 5) & 0x07FF); // 通道2 (云台pitch) + rc_ctrl[TEMP].rc.ch[2] = ((sbus_buf[3] >> 6 | sbus_buf[4] << 2 | sbus_buf[5] << 10) & 0x07FF); // 通道3 (前后) + rc_ctrl[TEMP].rc.ch[3] = ((sbus_buf[5] >> 1 | sbus_buf[6] << 7) & 0x07FF); // 通道4 (左右) + rc_ctrl[TEMP].rc.ch[4] = ((sbus_buf[6] >> 4 | sbus_buf[7] << 4) & 0x07FF); // 通道5 (SWA) + rc_ctrl[TEMP].rc.ch[5] = ((sbus_buf[7] >> 7 | sbus_buf[8] << 1 | sbus_buf[9] << 9) & 0x07FF); // 通道6 (SWB) + rc_ctrl[TEMP].rc.ch[6] = ((sbus_buf[9] >> 2 | sbus_buf[10] << 6) & 0x07FF); // 通道7 (SWC) + rc_ctrl[TEMP].rc.ch[7] = ((sbus_buf[10] >> 5 | sbus_buf[11] << 3) & 0x07FF); // 通道8 (SWD) + rc_ctrl[TEMP].rc.ch[8] = ((sbus_buf[12] | sbus_buf[13] << 8) & 0x07FF); // 通道9 (VrA旋钮) + rc_ctrl[TEMP].rc.ch[9] = ((sbus_buf[13] >> 3 | sbus_buf[14] << 5) & 0x07FF); // 通道10 (VrB旋钮) + + // rc_ctrl[TEMP].rc.ch[0] = ((sbus_buf[0] | sbus_buf[1] << 8) & 0x07FF); // 通道1 (云台yaw) + // rc_ctrl[TEMP].rc.ch[1] = ((sbus_buf[1] >> 3 | sbus_buf[2] << 5) & 0x07FF); // 通道2 (云台pitch) + // rc_ctrl[TEMP].rc.ch[2] = ((sbus_buf[2] >> 6 | sbus_buf[3] << 2 | sbus_buf[4] << 10) & 0x07FF); // 通道3 (前后) + // rc_ctrl[TEMP].rc.ch[3] = ((sbus_buf[4] >> 1 | sbus_buf[5] << 7) & 0x07FF); // 通道4 (左右) + // rc_ctrl[TEMP].rc.ch[4] = ((sbus_buf[5] >> 4 | sbus_buf[6] << 4) & 0x07FF); // 通道5 (SWA) + // rc_ctrl[TEMP].rc.ch[5] = ((sbus_buf[6] >> 7 | sbus_buf[7] << 1 | sbus_buf[8] << 9) & 0x07FF); // 通道6 (SWB) + // rc_ctrl[TEMP].rc.ch[6] = ((sbus_buf[8] >> 2 | sbus_buf[9] << 6) & 0x07FF); // 通道7 (SWC) + // rc_ctrl[TEMP].rc.ch[7] = ((sbus_buf[9] >> 5 | sbus_buf[10] << 3) & 0x07FF); // 通道8 (SWD) + // rc_ctrl[TEMP].rc.ch[8] = ((sbus_buf[11] | sbus_buf[12] << 8) & 0x07FF); // 通道9 (VrA旋钮) + // rc_ctrl[TEMP].rc.ch[9] = ((sbus_buf[12] >> 3 | sbus_buf[13] << 5) & 0x07FF); // 通道10 (VrB旋钮) + + rc_ctrl[TEMP].sw_a = (rc_ctrl[TEMP].rc.ch[4] == 0x00F0) ? RC_SW_UP : RC_SW_DOWN; + rc_ctrl[TEMP].sw_b = rc_ctrl[TEMP].rc.ch[5]; //3档 + rc_ctrl[TEMP].sw_c = rc_ctrl[TEMP].rc.ch[6]; //3档 + rc_ctrl[TEMP].sw_d = (rc_ctrl[TEMP].rc.ch[7] == 0x00F0) ? RC_SW_UP : RC_SW_DOWN; + + // 解析SWB状态 + if (rc_ctrl[TEMP].rc.ch[6] == 0x00F0) + { + rc_ctrl[TEMP].sw_b = RC_SW_UP; + } + else if (rc_ctrl[TEMP].rc.ch[6] == 0x0400) + { + rc_ctrl[TEMP].sw_b = RC_SW_MID; + } + else if (rc_ctrl[TEMP].rc.ch[6] == 0x070F) + { + rc_ctrl[TEMP].sw_b = RC_SW_DOWN; + } + else + { + rc_ctrl[TEMP].sw_b = 0; + } + + if (rc_ctrl[TEMP].sw_b != rc_ctrl[TEMP].sw_b_last) + { + if (rc_ctrl[TEMP].sw_b == RC_SW_UP) + { + rc_ctrl[TEMP].sw_b_midtoup_flag = 1; + rc_ctrl[TEMP].sw_b_uptomid_flag = 0; + rc_ctrl[TEMP].sw_b_midtodown_flag = 0; + } + else if (rc_ctrl[TEMP].sw_b == RC_SW_MID) + { + rc_ctrl[TEMP].sw_b_midtoup_flag = 0; + rc_ctrl[TEMP].sw_b_uptomid_flag = 1; + rc_ctrl[TEMP].sw_b_midtodown_flag = 0; + } + else if (rc_ctrl[TEMP].sw_b == RC_SW_DOWN) + { + rc_ctrl[TEMP].sw_b_midtoup_flag = 0; + rc_ctrl[TEMP].sw_b_uptomid_flag = 0; + rc_ctrl[TEMP].sw_b_midtodown_flag = 1; + } + } + rc_ctrl[TEMP].sw_b_last = rc_ctrl[TEMP].sw_b; + + // 解析SWC状态 + if (rc_ctrl[TEMP].rc.ch[6] == 0x00F0) + { + rc_ctrl[TEMP].sw_c = RC_SW_UP; + } + else if (rc_ctrl[TEMP].rc.ch[6] == 0x0400) + { + rc_ctrl[TEMP].sw_c = RC_SW_MID; + } + else if (rc_ctrl[TEMP].rc.ch[6] == 0x070F) + { + rc_ctrl[TEMP].sw_c = RC_SW_DOWN; + } + else + { + rc_ctrl[TEMP].sw_c = 0; + } + + if (rc_ctrl[TEMP].sw_c != rc_ctrl[TEMP].sw_c_last) + { + if (rc_ctrl[TEMP].sw_c == RC_SW_UP) + { + rc_ctrl[TEMP].sw_c_midtoup_flag = 1; + rc_ctrl[TEMP].sw_c_uptomid_flag = 0; + rc_ctrl[TEMP].sw_c_midtodown_flag = 0; + } + else if (rc_ctrl[TEMP].sw_c == RC_SW_MID) + { + rc_ctrl[TEMP].sw_c_midtoup_flag = 0; + rc_ctrl[TEMP].sw_c_uptomid_flag = 1; + rc_ctrl[TEMP].sw_c_midtodown_flag = 0; + } + else if (rc_ctrl[TEMP].sw_c == RC_SW_DOWN) + { + rc_ctrl[TEMP].sw_c_midtoup_flag = 0; + rc_ctrl[TEMP].sw_c_uptomid_flag = 0; + rc_ctrl[TEMP].sw_c_midtodown_flag = 1; + } + } + rc_ctrl[TEMP].sw_c_last = rc_ctrl[TEMP].sw_c; + + // SWA和SWB的状态变化 + if (rc_ctrl[TEMP].sw_a != rc_ctrl[TEMP].sw_a_last) + { + rc_ctrl[TEMP].sw_a_up_to_down_flag = (rc_ctrl[TEMP].sw_a == RC_SW_UP) ? 1 : 0; + } + rc_ctrl[TEMP].sw_a_last = rc_ctrl[TEMP].sw_a; + + // if (rc_ctrl[TEMP].sw_b != rc_ctrl[TEMP].sw_b_last) { + // rc_ctrl[TEMP].sw_b_up_to_down_flag = (rc_ctrl[TEMP].sw_b == RC_SW_UP) ? 1 : 0; + // } + // rc_ctrl[TEMP].sw_b_last = rc_ctrl[TEMP].sw_b; + + // SWD的状态变化 + if (rc_ctrl[TEMP].sw_d != rc_ctrl[TEMP].sw_d_last) + { + rc_ctrl[TEMP].sw_d_up_to_down_flag = (rc_ctrl[TEMP].sw_d == RC_SW_UP) ? 1 : 0; + } + rc_ctrl[TEMP].sw_d_last = rc_ctrl[TEMP].sw_d; + + // 修正 ch1~ch4 + rc_ctrl[TEMP].ch1 = rc_ctrl[TEMP].rc.ch[0] - 1024; + rc_ctrl[TEMP].ch2 = rc_ctrl[TEMP].rc.ch[1] - 1024; + rc_ctrl[TEMP].ch3 = rc_ctrl[TEMP].rc.ch[2] - 1024; + rc_ctrl[TEMP].ch4 = rc_ctrl[TEMP].rc.ch[3] - 1024; + // ...existing code... + + memcpy(&rc_ctrl[LAST], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断 +} +#endif + +/** + * @brief 对sbus_to_rc的简单封装,用于注册到bsp_usart的回调函数中 + * + */ +static void RemoteControlRxCallback() +{ + sbus_to_rc(rc_usart_instance->recv_buff); // 进行协议解析 + DaemonReload(rc_daemon_instance); // 先喂狗 +} + +/** + * @brief 遥控器离线的回调函数,注册到守护进程中,串口掉线时调用 + * + */ +static void RCLostCallback(void *id) +{ + memset(rc_ctrl, 0, sizeof(rc_ctrl)); // 清空遥控器数据 + USARTServiceInit(rc_usart_instance); // 尝试重新启动接收 + + RobotMode = REMOTE_NOT_CONNECTED; + + // LEDErrLog(0, LED_COLOR_R); // 红灯常亮 表示遥控器离线 +} + +RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle) +{ + USART_Init_Config_s conf; + conf.module_callback = RemoteControlRxCallback; + conf.usart_handle = rc_usart_handle; + conf.recv_buff_size = REMOTE_CONTROL_FRAME_SIZE; + + rc_usart_instance = USARTRegister(&conf); + + // 进行守护进程的注册,用于定时检查遥控器是否正常工作 + Daemon_Init_Config_s daemon_conf = { + .reload_count = 10, // 100ms未收到数据视为离线,遥控器的接收频率实际上是1000/14Hz(大约70Hz) + .callback = RCLostCallback, + .owner_id = NULL, // 只有1个遥控器,不需要owner_id + }; + rc_daemon_instance = DaemonRegister(&daemon_conf); + + rc_init_flag = 1; + return rc_ctrl; +} + +uint8_t RemoteControlIsOnline() +{ + if (rc_init_flag) + return DaemonIsOnline(rc_daemon_instance); + return 0; +} diff --git a/User_Code/module/periph/remote_control/rc.h b/User_Code/module/periph/remote_control/rc.h index 6bb3b45..130bd5e 100644 --- a/User_Code/module/periph/remote_control/rc.h +++ b/User_Code/module/periph/remote_control/rc.h @@ -6,3 +6,149 @@ #define TRONONEH7_SCAFFOLD_RC_H #endif //TRONONEH7_SCAFFOLD_RC_H + +/** + * @file remote.h + * @author TuxMonkey (nqx_2004@qq.com) + * @brief 遥控器模块 + * @version 0.1 + * @date 2025-01-06 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef REMOTE_H +#define REMOTE_H + +//@todo:测试define +#define REMOTE_FS_I6X + +#include "stdint.h" +#include "main.h" +#include "usart.h" +#include "key_define.h" +#include "robot_def.h" + +// 检查接收值是否出错 +#define RC_CH_VALUE_MIN ((uint16_t)0) +#define RC_CH_VALUE_OFFSET ((uint16_t)1024) +#define RC_CH_VALUE_MAX ((uint16_t)2048) + +/* ----------------------- RC Switch Definition----------------------------- */ +#define RC_SW_UP ((uint16_t)1) // 开关向上时的值 +#define RC_SW_MID ((uint16_t)3) // 开关中间时的值 +#define RC_SW_DOWN ((uint16_t)2) // 开关向下时的值 +// 三个判断开关状态的宏 +#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) + + +#ifdef REMOTE_DJI_DT7 +typedef struct +{ + struct + { + int16_t rocker_l_; // 左水平 + int16_t rocker_l1; // 左竖直 + int16_t rocker_r_; // 右水平 + int16_t rocker_r1; // 右竖直 + int16_t dial; // 侧边拨轮 + + uint8_t switch_left; // 左侧开关 + uint8_t switch_right; // 右侧开关 + } rc; + + 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~倍 + + uint8_t key_count[3][16]; +} RC_ctrl_t; +#endif // REMOTE_DJI_DT7 + +// @todo 当前结构体嵌套过深,需要进行优化 +#ifdef REMOTE_FS_I6X +typedef struct +{ + struct + { + // int16_t rocker_l_; // 左水平 + // int16_t rocker_l1; // 左竖直 + // int16_t rocker_r_; // 右水平 + // int16_t rocker_r1; // 右竖直 + // int16_t dial; // 侧边拨轮 + + // uint8_t switch_left; // 左侧开关 + // uint8_t switch_right; // 右侧开关 + + int16_t ch[10]; + } rc; + + struct + { + int16_t x; + int16_t y; + int16_t z; + uint8_t press_l; + uint8_t press_r; + } mouse; + + uint8_t sw_a; // SWA + uint8_t sw_b; // SWB + uint16_t sw_c; // SWC 3 + uint8_t sw_d; // SWD + int16_t ch1; // 通道1 (云台yaw) + int16_t ch2; // 通道2 (云台pitch) + int16_t ch3; // 通道3 (前后) + int16_t ch4; // 通道4 (左右) + uint8_t sw_a_last; + uint8_t sw_b_last; + uint16_t sw_c_last; + uint8_t sw_d_last; + + uint8_t sw_a_up_to_down_flag; + + uint8_t sw_b_midtoup_flag; + uint8_t sw_b_uptomid_flag; + uint8_t sw_b_midtodown_flag; + uint8_t sw_b_downtomid_flag; + + uint8_t sw_c_midtoup_flag; + uint8_t sw_c_uptomid_flag; + uint8_t sw_c_midtodown_flag; + uint8_t sw_c_downtomid_flag; + uint8_t sw_d_up_to_down_flag; + uint16_t online; + + Key_t key[3]; // 改为位域后的键盘索引,空间减少8倍,速度增加16~倍 + + uint8_t key_count[3][16]; +} RC_ctrl_t; +#endif // FSI6X +/* ------------------------- Internal Data ----------------------------------- */ + +/** + * @brief 初始化遥控器,该函数会将遥控器注册到串口 + * + * @attention 注意分配正确的串口硬件,遥控器在C板上使用USART3 + * + */ +RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle); + +/** + * @brief 检查遥控器是否在线,若尚未初始化也视为离线 + * + * @return uint8_t 1:在线 0:离线 + */ +uint8_t RemoteControlIsOnline(); + +#endif // REMOTE_H diff --git a/User_Code/module/software/daemon/daemon.c b/User_Code/module/software/daemon/daemon.c new file mode 100644 index 0000000..8e9d905 --- /dev/null +++ b/User_Code/module/software/daemon/daemon.c @@ -0,0 +1,64 @@ +#include "daemon.h" +#include "bsp_dwt.h" +#include "stdlib.h" +#include "memory.h" + +/* 用于保存所有的daemon instance */ +static Daemon_Instance *daemon_instances[DAEMON_MAX_NUM]; +static uint8_t idx; // 用于记录当前注册的daemon数量 + +Daemon_Instance *DaemonRegister(Daemon_Init_Config_s *config) +{ + Daemon_Instance *daemon_instance = (Daemon_Instance *) malloc(sizeof(Daemon_Instance)); + memset(daemon_instance, 0, sizeof(Daemon_Instance)); + + daemon_instance->id = config->owner_id; + daemon_instance->reload_count = config->reload_count == 0 ? 100 : config->reload_count; // 默认重载值为100 + daemon_instance->callback = config->callback; + daemon_instance->temp_count = config->init_count == 0 ? 100 : config->init_count; // 默认上线等待时间为100 + daemon_instance->temp_count = config->reload_count; + + daemon_instances[idx++] = daemon_instance; + + return daemon_instance; +} + +/** + * @brief 当模块收到新的数据或进行其他动作时,调用该函数重载temp_count,相当于"喂狗" + * + * @param daemon daemon实例指针 + */ +void DaemonReload(Daemon_Instance *daemon) +{ + daemon->temp_count = daemon->reload_count; +} + +/** + * @brief 确认模块是否离线 + * + * @param daemon + * @return uint8_t 若在线且工作正常,返回1;否则返回零. 后续根据异常类型和离线状态等进行优化. + */ +uint8_t DaemonIsOnline(Daemon_Instance *daemon) +{ + return daemon->temp_count > 0; +} + +/** + * @brief 放入rtos中,会给每个daemon实例的temp_count按频率进行递减操作. + * 模块成功接受数据或成功操作则会重载temp_count的值为reload_count. + * + */ +void DaemonTask(void) +{ + Daemon_Instance *daemon; + for (uint8_t i = 0; i < idx; i++) + { + daemon = daemon_instances[i]; + if (daemon->temp_count > 0) // 如果计数器还有值,说明上一次喂狗后还没有超时,则计数器减一 + daemon->temp_count--; + else if (daemon->callback != NULL) // 等于零说明超时了,调用回调函数(如果有的话) + daemon->callback(daemon->id); + // @todo 可以加入蜂鸣器或者led等提示 + } +} diff --git a/User_Code/module/software/daemon/daemon.h b/User_Code/module/software/daemon/daemon.h index 8e5e321..23b48a7 100644 --- a/User_Code/module/software/daemon/daemon.h +++ b/User_Code/module/software/daemon/daemon.h @@ -1,8 +1,72 @@ -// -// Created by ASUS on 2025/11/17. -// +/** +* @file daemon.h + * @author TuxMonkey (nqx_2004@qq.com) + * @brief 守护进程 + * @version 0.1 + * @date 2025-01-04 + * + * @copyright Copyright (c) 2025 + * + */ -#ifndef TRONONEH7_SCAFFOLD_DAEMON_H -#define TRONONEH7_SCAFFOLD_DAEMON_H +#ifndef DAEMON_H +#define DAEMON_H -#endif //TRONONEH7_SCAFFOLD_DAEMON_H +#include "stdint.h" +#include "memory.h" + +#define DAEMON_MAX_NUM 32 // 支持的最大守护进程数量 + +/* 模块离线处理函数指针 */ +typedef void (*offline_callback)(void *); + +typedef struct daemon_ins +{ + uint16_t reload_count; // 重载值 + offline_callback callback; // 离线处理函数,当模块离线时调用 + + uint16_t temp_count; // 当前值,减为零说明模块离线或异常 + void *id; // 模块id,用于标识模块,初始化时传入 +} Daemon_Instance; + +/* daemon初始化配置 */ +typedef struct +{ + uint16_t reload_count; // 实际上这是app唯一需要设置的值? + uint16_t init_count; // 上线等待时间,有些模块需要收到主控的指令才会反馈报文,或pc等需要开机时间 + offline_callback callback; // 异常处理函数,当模块发生异常时会被调用 + + void *owner_id; // id取拥有daemon的实例的地址,如DJIMotorInstance*,cast成void*类型 +} Daemon_Init_Config_s; + +/** + * @brief 注册一个daemon实例 + * + * @param config 初始化配置 + * @return DaemonInstance* 返回实例指针 + */ +Daemon_Instance *DaemonRegister(Daemon_Init_Config_s *config); + +/** + * @brief 当模块收到新的数据或进行其他动作时,调用该函数重载temp_count,相当于"喂狗" + * + * @param daemon daemon实例指针 + */ +void DaemonReload(Daemon_Instance *daemon); + +/** + * @brief 确认模块是否离线 + * + * @param daemon + * @return uint8_t 若在线且工作正常,返回1;否则返回零. 后续根据异常类型和离线状态等进行优化. + */ +uint8_t DaemonIsOnline(Daemon_Instance *daemon); + +/** + * @brief 放入rtos中,会给每个daemon实例的temp_count按频率进行递减操作. + * 模块成功接受数据或成功操作则会重载temp_count的值为reload_count. + * + */ +void DaemonTask(void); + +#endif // DAEMON_H