41 Commits
v0.2 ... master

Author SHA1 Message Date
TuxMonkey
bd7f8f2962 silentmode(ERROR!) 2026-07-20 14:10:32 +08:00
TuxMonkey
4f7817f9de start song 2026-07-16 13:45:09 +08:00
TuxMonkey
59de1fafa4 start song 2026-07-16 13:38:48 +08:00
TuxMonkey
47f5e4ddab add DMA remote control 2026-07-14 21:58:07 +08:00
TuxMonkey
f4c0424c45 add DMA remote control 2026-07-14 21:56:46 +08:00
TuxMonkey
591707d9fc changed FDCAN!PowerModule OK! 2026-03-16 21:56:27 +08:00
TuxMonkey
aa6eefda50 changed FDCAN!PowerModule OK! 2026-03-16 18:15:05 +08:00
TuxMonkey
e84c3e11f3 狗好了,但是PowerMeterDecode还是g的,*rxbuff是好的,但是解析出来都是0.idxOK 头疼 2026-03-12 22:11:13 +08:00
TuxMonkey
f06df047db 狗好了,但是PowerMeterDecode还是g的,*rxbuff是好的,但是解析出来都是0.idxOK 头疼 2026-03-11 22:00:57 +08:00
TuxMonkey
4c2bff6257 狗好了,但是PowerMeterDecode还是g的,*rxbuff是好的,但是解析出来都是0.idxOK 头疼 2026-03-09 22:02:32 +08:00
TuxMonkey
0ccdd0a0d5 修复了吗?没有,很难的啦111 2026-03-09 21:43:44 +08:00
TuxMonkey
e3f5951881 修复了吗?没有,很难的啦 2026-03-09 18:28:45 +08:00
TuxMonkey
a96e9e939c 修复了吗?没有,很难的啦 2026-03-09 17:47:13 +08:00
79afbaf68d daemon init success 2026-03-09 02:49:56 +08:00
28006c3bd0 daemon init success 2026-03-09 02:46:48 +08:00
cf1bc7b2ae daemon init failed 2026-03-09 02:35:08 +08:00
9c58a09eab xidipower test 2026-03-09 01:57:43 +08:00
TuxMonkey
5145d60794 not powermeter data 2026-03-08 22:08:36 +08:00
TuxMonkey
b5863332b6 not powermeter data 2026-03-08 22:08:13 +08:00
TuxMonkey
2f0ca6d906 add devcmdtask but couldnt recv data 2026-03-07 21:59:47 +08:00
50edf7dedb xidipower test 2026-03-07 01:49:39 +08:00
cf161df939 add DM motor drv dwt delay times 2026-03-05 12:23:34 +08:00
483d5f0ac4 add DM motor drv 2026-03-05 02:06:41 +08:00
db95e9e44d Merge remote-tracking branch 'origin/master' 2026-03-05 00:58:01 +08:00
1e75c1a5e1 add lk motor drv 2026-03-05 00:56:03 +08:00
c3fc805aca changed bspfdcan 2026-03-05 00:44:44 +08:00
TuxMonkey
596d3d4c65 halfsteering 2026-03-03 21:44:39 +08:00
6d7d23ebba add some files 2026-03-03 01:30:09 +08:00
ed4e6ff0d1 add some files 2026-03-02 17:34:48 +08:00
f8b8616966 add some files 2026-03-02 11:14:12 +08:00
d8563db2be Remote To DMA 2026-03-01 17:40:22 +08:00
ede790e849 CAN CAN NEED 2026-02-24 23:47:15 +08:00
216c96c2c6 CAN CAN NEED 2026-02-24 16:46:57 +08:00
522d6a4545 CAN CAN NEED 2026-02-24 16:43:57 +08:00
c0c7658b39 Regenerated HAL Drivers to Support Power 5V EN/24V EN,Also fixed FDCAN3 Tx Fifo Queue Elmts Nbr/Baudrate. 2026-02-24 16:21:12 +08:00
17ce43a7ef Regenerated HAL Drivers to Support Power 5V EN/24V EN,Also fixed FDCAN3 Tx Fifo Queue Elmts Nbr/Baudrate. 2026-02-24 16:05:17 +08:00
ebe9ad087e Regenerated HAL Drivers to Support Power 5V EN/24V EN,Also fixed FDCAN3 Tx Fifo Queue Elmts Nbr/Baudrate. 2026-02-24 15:35:38 +08:00
e515c97955 great changes 2026-02-24 14:42:54 +08:00
a941a3719a great changes 2026-02-23 23:49:47 +08:00
58299949c5 dji motor but not tested 2026-02-23 13:32:10 +08:00
834f9e573c 双板通信inited 2026-02-22 00:16:04 +08:00
85 changed files with 4878 additions and 1325 deletions

View File

@@ -179,13 +179,12 @@ file(GLOB_RECURSE COMMON_SOURCES
"${PROJECT_SOURCE_DIR}/User_Code/bsp/*.cpp"
"${PROJECT_SOURCE_DIR}/User_Code/module/*.c"
"${PROJECT_SOURCE_DIR}/User_Code/module/*.cpp"
# 包含 application 根目录下的 robot.c 等文件 (不递归)
# 包含 application 根目录下的 robot.c 等文件 (不递归) todo 需重写
"${PROJECT_SOURCE_DIR}/User_Code/application/*.c"
"${PROJECT_SOURCE_DIR}/User_Code/application/*.cpp"
# 包含 application 下的通用模块 (根据目录结构递归)
# 包含 application 下的通用模块 (根据目录结构递归) todo 需重写
"${PROJECT_SOURCE_DIR}/User_Code/application/chassis_app/*.c" "${PROJECT_SOURCE_DIR}/User_Code/application/chassis_app/*.cpp"
"${PROJECT_SOURCE_DIR}/User_Code/application/gimbal_app/*.c" "${PROJECT_SOURCE_DIR}/User_Code/application/gimbal_app/*.cpp"
"${PROJECT_SOURCE_DIR}/User_Code/application/shoot_app/*.c" "${PROJECT_SOURCE_DIR}/User_Code/application/shoot_app/*.cpp"
"${PROJECT_SOURCE_DIR}/User_Code/application/gimbal_app/*.c" "${PROJECT_SOURCE_DIR}/User_Code/application/gimbal_app/*.cpp" "${PROJECT_SOURCE_DIR}/User_Code/application/shoot_app/*.c" "${PROJECT_SOURCE_DIR}/User_Code/application/shoot_app/*.cpp"
"${PROJECT_SOURCE_DIR}/User_Code/application/indicator_app/*.c" "${PROJECT_SOURCE_DIR}/User_Code/application/indicator_app/*.cpp"
"${PROJECT_SOURCE_DIR}/User_Code/application/vision_app/*.c" "${PROJECT_SOURCE_DIR}/User_Code/application/vision_app/*.cpp"
)

View File

@@ -57,6 +57,12 @@ void Error_Handler(void);
/* USER CODE END EFP */
/* Private defines -----------------------------------------------------------*/
#define Power2_Pin GPIO_PIN_13
#define Power2_GPIO_Port GPIOC
#define Power1_Pin GPIO_PIN_14
#define Power1_GPIO_Port GPIOC
#define Power_5V_EN_Pin GPIO_PIN_15
#define Power_5V_EN_GPIO_Port GPIOC
#define ACC_CS_Pin GPIO_PIN_0
#define ACC_CS_GPIO_Port GPIOC
#define GYRO_CS_Pin GPIO_PIN_3

View File

@@ -62,6 +62,8 @@ void DMA1_Stream6_IRQHandler(void);
void ADC_IRQHandler(void);
void FDCAN1_IT0_IRQHandler(void);
void FDCAN2_IT0_IRQHandler(void);
void FDCAN1_IT1_IRQHandler(void);
void FDCAN2_IT1_IRQHandler(void);
void SPI1_IRQHandler(void);
void SPI2_IRQHandler(void);
void USART1_IRQHandler(void);
@@ -80,6 +82,7 @@ void OTG_HS_IRQHandler(void);
void UART7_IRQHandler(void);
void USART10_IRQHandler(void);
void FDCAN3_IT0_IRQHandler(void);
void FDCAN3_IT1_IRQHandler(void);
void TIM23_IRQHandler(void);
/* USER CODE BEGIN EFP */

View File

@@ -42,9 +42,9 @@ void MX_FDCAN1_Init(void)
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = ENABLE;
hfdcan1.Init.AutoRetransmission = DISABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = ENABLE;
hfdcan1.Init.ProtocolException = DISABLE;
hfdcan1.Init.NominalPrescaler = 3;
hfdcan1.Init.NominalSyncJumpWidth = 10;
hfdcan1.Init.NominalTimeSeg1 = 29;
@@ -54,11 +54,11 @@ void MX_FDCAN1_Init(void)
hfdcan1.Init.DataTimeSeg1 = 29;
hfdcan1.Init.DataTimeSeg2 = 10;
hfdcan1.Init.MessageRAMOffset = 0;
hfdcan1.Init.StdFiltersNbr = 1;
hfdcan1.Init.StdFiltersNbr = 14;
hfdcan1.Init.ExtFiltersNbr = 0;
hfdcan1.Init.RxFifo0ElmtsNbr = 32;
hfdcan1.Init.RxFifo0ElmtsNbr = 4;
hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.RxFifo1ElmtsNbr = 0;
hfdcan1.Init.RxFifo1ElmtsNbr = 4;
hfdcan1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan1.Init.RxBuffersNbr = 0;
hfdcan1.Init.RxBufferSize = FDCAN_DATA_BYTES_8;
@@ -90,9 +90,9 @@ void MX_FDCAN2_Init(void)
hfdcan2.Instance = FDCAN2;
hfdcan2.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
hfdcan2.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan2.Init.AutoRetransmission = ENABLE;
hfdcan2.Init.AutoRetransmission = DISABLE;
hfdcan2.Init.TransmitPause = DISABLE;
hfdcan2.Init.ProtocolException = ENABLE;
hfdcan2.Init.ProtocolException = DISABLE;
hfdcan2.Init.NominalPrescaler = 3;
hfdcan2.Init.NominalSyncJumpWidth = 10;
hfdcan2.Init.NominalTimeSeg1 = 29;
@@ -102,11 +102,11 @@ void MX_FDCAN2_Init(void)
hfdcan2.Init.DataTimeSeg1 = 29;
hfdcan2.Init.DataTimeSeg2 = 10;
hfdcan2.Init.MessageRAMOffset = 853;
hfdcan2.Init.StdFiltersNbr = 1;
hfdcan2.Init.StdFiltersNbr = 14;
hfdcan2.Init.ExtFiltersNbr = 0;
hfdcan2.Init.RxFifo0ElmtsNbr = 0;
hfdcan2.Init.RxFifo0ElmtsNbr = 4;
hfdcan2.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan2.Init.RxFifo1ElmtsNbr = 32;
hfdcan2.Init.RxFifo1ElmtsNbr = 4;
hfdcan2.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan2.Init.RxBuffersNbr = 0;
hfdcan2.Init.RxBufferSize = FDCAN_DATA_BYTES_8;
@@ -138,29 +138,29 @@ void MX_FDCAN3_Init(void)
hfdcan3.Instance = FDCAN3;
hfdcan3.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
hfdcan3.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan3.Init.AutoRetransmission = ENABLE;
hfdcan3.Init.AutoRetransmission = DISABLE;
hfdcan3.Init.TransmitPause = DISABLE;
hfdcan3.Init.ProtocolException = ENABLE;
hfdcan3.Init.NominalPrescaler = 24;
hfdcan3.Init.ProtocolException = DISABLE;
hfdcan3.Init.NominalPrescaler = 3;
hfdcan3.Init.NominalSyncJumpWidth = 10;
hfdcan3.Init.NominalTimeSeg1 = 2;
hfdcan3.Init.NominalTimeSeg2 = 2;
hfdcan3.Init.NominalTimeSeg1 = 29;
hfdcan3.Init.NominalTimeSeg2 = 10;
hfdcan3.Init.DataPrescaler = 3;
hfdcan3.Init.DataSyncJumpWidth = 10;
hfdcan3.Init.DataTimeSeg1 = 29;
hfdcan3.Init.DataTimeSeg2 = 10;
hfdcan3.Init.MessageRAMOffset = 1706;
hfdcan3.Init.StdFiltersNbr = 1;
hfdcan3.Init.StdFiltersNbr = 14;
hfdcan3.Init.ExtFiltersNbr = 0;
hfdcan3.Init.RxFifo0ElmtsNbr = 0;
hfdcan3.Init.RxFifo0ElmtsNbr = 4;
hfdcan3.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan3.Init.RxFifo1ElmtsNbr = 32;
hfdcan3.Init.RxFifo1ElmtsNbr = 4;
hfdcan3.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_8;
hfdcan3.Init.RxBuffersNbr = 0;
hfdcan3.Init.RxBufferSize = FDCAN_DATA_BYTES_8;
hfdcan3.Init.TxEventsNbr = 0;
hfdcan3.Init.TxBuffersNbr = 0;
hfdcan3.Init.TxFifoQueueElmtsNbr = 6;
hfdcan3.Init.TxFifoQueueElmtsNbr = 32;
hfdcan3.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
hfdcan3.Init.TxElmtSize = FDCAN_DATA_BYTES_8;
if (HAL_FDCAN_Init(&hfdcan3) != HAL_OK)
@@ -216,6 +216,8 @@ void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle)
/* FDCAN1 interrupt Init */
HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);
HAL_NVIC_SetPriority(FDCAN1_IT1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(FDCAN1_IT1_IRQn);
/* USER CODE BEGIN FDCAN1_MspInit 1 */
/* USER CODE END FDCAN1_MspInit 1 */
@@ -256,6 +258,8 @@ void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle)
/* FDCAN2 interrupt Init */
HAL_NVIC_SetPriority(FDCAN2_IT0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn);
HAL_NVIC_SetPriority(FDCAN2_IT1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(FDCAN2_IT1_IRQn);
/* USER CODE BEGIN FDCAN2_MspInit 1 */
/* USER CODE END FDCAN2_MspInit 1 */
@@ -296,6 +300,8 @@ void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle)
/* FDCAN3 interrupt Init */
HAL_NVIC_SetPriority(FDCAN3_IT0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(FDCAN3_IT0_IRQn);
HAL_NVIC_SetPriority(FDCAN3_IT1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(FDCAN3_IT1_IRQn);
/* USER CODE BEGIN FDCAN3_MspInit 1 */
/* USER CODE END FDCAN3_MspInit 1 */
@@ -324,6 +330,7 @@ void HAL_FDCAN_MspDeInit(FDCAN_HandleTypeDef* fdcanHandle)
/* FDCAN1 interrupt Deinit */
HAL_NVIC_DisableIRQ(FDCAN1_IT0_IRQn);
HAL_NVIC_DisableIRQ(FDCAN1_IT1_IRQn);
/* USER CODE BEGIN FDCAN1_MspDeInit 1 */
/* USER CODE END FDCAN1_MspDeInit 1 */
@@ -347,6 +354,7 @@ void HAL_FDCAN_MspDeInit(FDCAN_HandleTypeDef* fdcanHandle)
/* FDCAN2 interrupt Deinit */
HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn);
HAL_NVIC_DisableIRQ(FDCAN2_IT1_IRQn);
/* USER CODE BEGIN FDCAN2_MspDeInit 1 */
/* USER CODE END FDCAN2_MspDeInit 1 */
@@ -370,6 +378,7 @@ void HAL_FDCAN_MspDeInit(FDCAN_HandleTypeDef* fdcanHandle)
/* FDCAN3 interrupt Deinit */
HAL_NVIC_DisableIRQ(FDCAN3_IT0_IRQn);
HAL_NVIC_DisableIRQ(FDCAN3_IT1_IRQn);
/* USER CODE BEGIN FDCAN3_MspDeInit 1 */
/* USER CODE END FDCAN3_MspDeInit 1 */

View File

@@ -33,7 +33,9 @@
// #include "bsp_dwt.h"
// // #include "ins_task.h"
// #include "bsp_log.h"
#include "dev_cmd.h"
#include "daemon.h"
#include "bsp_usart.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -131,35 +133,47 @@ const osThreadAttr_t instask_attributes = {
//@Todo:测试使用
osThreadId insTaskHandle;
void StartINSTASK(void const *argument);
void StartINSTASK(void *argument);
//@Todo:测试daemon(但是没有daemon注册)
const osThreadAttr_t daemon_attributes = {
.name = "daemon",
.priority = osPriorityAboveNormal, // 较高优先级
.stack_size = 1024 * 4 // 栈大小单位是字节通常是字数的4倍
};
//@Todo:测试使用
osThreadId daemonHandle;
void StartDaemonTask(void *argument); // <--- 新的,带参数的标准 RTOS 线程声明
const osThreadAttr_t buzzer_attributes = {
.name = "buzzer",
.priority = osPriorityNormal,
.stack_size = 512 * 4
};
osThreadId buzzerHandle;
extern void buzzerTask(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 */
@@ -194,8 +208,7 @@ 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 */
@@ -249,11 +262,17 @@ void MX_FREERTOS_Init(void)
//@Todo:INS_Task是测试版本
// 创建线程
insTaskHandle = osThreadNew(StartINSTASK, NULL, &instask_attributes);
//@Todo:daemon是测试版本
// 创建线程
daemonHandle = osThreadNew(StartDaemonTask, NULL, &daemon_attributes); // <--- 换成新的函数名
buzzerHandle = osThreadNew(buzzerTask, NULL, &buzzer_attributes);
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
@@ -406,7 +425,7 @@ __weak void RefereeTask(void *argument)
/* USER CODE BEGIN Application */
//@Todo:INS_Task是测试阶段使用。
__attribute__((noreturn)) void StartINSTASK(void const *argument)
__attribute__((noreturn)) void StartINSTASK(void *argument)
{
static float ins_start;
static float ins_dt;
@@ -425,7 +444,33 @@ __attribute__((noreturn)) void StartINSTASK(void const *argument)
}
}
/**
* @brief Function implementing the reference thread.
* @param argument: Not used
* @retval None
* @Todo:Deamon task 后期加入cmd和def
*/
/* USER CODE END Header_RefereeTask */
__attribute__((noreturn)) void StartDaemonTask(void *argument)
{
/* USER CODE BEGIN StartDaemonTask */
// LOGINFO("[freeRTOS] Daemon Task Start");
/* Infinite loop */
for (;;)
{
USARTServiceTask();
// 1. 执行核心的数据刷新逻辑
Daemon_Update();
// 2. 线程休眠 10ms (100Hz 运行频率)
osDelay(10);
}
/* USER CODE END StartDaemonTask */
}
// 注意:把你原来写在 freertos.c 里的那个 __weak void DaemonTask() 整个删掉,防止干扰!
/* USER CODE END Application */

View File

@@ -56,10 +56,10 @@ void MX_GPIO_Init(void)
__HAL_RCC_GPIOD_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14|ACC_CS_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, Power2_Pin|Power1_Pin|Power_5V_EN_Pin|GYRO_CS_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15|GYRO_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ACC_CS_GPIO_Port, ACC_CS_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, power2_Pin|power1_Pin, GPIO_PIN_SET);
@@ -76,8 +76,8 @@ void MX_GPIO_Init(void)
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(cs3_GPIO_Port, cs3_Pin, GPIO_PIN_SET);
/*Configure GPIO pins : PC14 PC15 */
GPIO_InitStruct.Pin = GPIO_PIN_14|GPIO_PIN_15;
/*Configure GPIO pins : Power2_Pin Power1_Pin Power_5V_EN_Pin */
GPIO_InitStruct.Pin = Power2_Pin|Power1_Pin|Power_5V_EN_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

View File

@@ -327,6 +327,34 @@ void FDCAN2_IT0_IRQHandler(void)
/* USER CODE END FDCAN2_IT0_IRQn 1 */
}
/**
* @brief This function handles FDCAN1 interrupt 1.
*/
void FDCAN1_IT1_IRQHandler(void)
{
/* USER CODE BEGIN FDCAN1_IT1_IRQn 0 */
/* USER CODE END FDCAN1_IT1_IRQn 0 */
HAL_FDCAN_IRQHandler(&hfdcan1);
/* USER CODE BEGIN FDCAN1_IT1_IRQn 1 */
/* USER CODE END FDCAN1_IT1_IRQn 1 */
}
/**
* @brief This function handles FDCAN2 interrupt 1.
*/
void FDCAN2_IT1_IRQHandler(void)
{
/* USER CODE BEGIN FDCAN2_IT1_IRQn 0 */
/* USER CODE END FDCAN2_IT1_IRQn 0 */
HAL_FDCAN_IRQHandler(&hfdcan2);
/* USER CODE BEGIN FDCAN2_IT1_IRQn 1 */
/* USER CODE END FDCAN2_IT1_IRQn 1 */
}
/**
* @brief This function handles SPI1 global interrupt.
*/
@@ -579,6 +607,20 @@ void FDCAN3_IT0_IRQHandler(void)
/* USER CODE END FDCAN3_IT0_IRQn 1 */
}
/**
* @brief This function handles FDCAN3 interrupt 1.
*/
void FDCAN3_IT1_IRQHandler(void)
{
/* USER CODE BEGIN FDCAN3_IT1_IRQn 0 */
/* USER CODE END FDCAN3_IT1_IRQn 0 */
HAL_FDCAN_IRQHandler(&hfdcan3);
/* USER CODE BEGIN FDCAN3_IT1_IRQn 1 */
/* USER CODE END FDCAN3_IT1_IRQn 1 */
}
/**
* @brief This function handles TIM23 global interrupt.
*/

View File

@@ -55,7 +55,7 @@ void MX_UART5_Init(void)
huart5.Instance = UART5;
huart5.Init.BaudRate = 100000;
huart5.Init.WordLength = UART_WORDLENGTH_9B;
huart5.Init.StopBits = UART_STOPBITS_1;
huart5.Init.StopBits = UART_STOPBITS_2;
huart5.Init.Parity = UART_PARITY_EVEN;
huart5.Init.Mode = UART_MODE_RX;
huart5.Init.HwFlowCtl = UART_HWCONTROL_NONE;
@@ -361,8 +361,8 @@ void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
hdma_uart5_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_uart5_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_uart5_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_uart5_rx.Init.Mode = DMA_CIRCULAR;
hdma_uart5_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_uart5_rx.Init.Mode = DMA_NORMAL;
hdma_uart5_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_uart5_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_uart5_rx) != HAL_OK)
{

View File

@@ -0,0 +1,144 @@
# SBUS 遥控接收修复说明
这份记录用来说明这次从“遥控器收不到数据 / `rc_ctrl` 不变化”一路排查到当前稳定版本的主要修改点。写得偏简略重点放在为什么改、DMA 怎么配、哪些文件动过,以及最后对当前 `git diff` 的复核结论。
## 1. 一开始的问题和定位思路
最开始的现象是:遥控器链路没有让 `rc_ctrl` 更新,后面又观察到 `rc_debug.rx_event_count` 也不增长,说明问题不只是协议解析,而是 UART/DMA 接收事件本身没有稳定进入。
排查时按链路分层看:
1. **初始化链路**:发现遥控器模块没有在 `RobotInit()` 里真正初始化,所以补了 `RemoteControlInit(&huart5)`
2. **UART 协议配置**:用户确认 SBUS 硬件已经做了反向,所以保持 UART5 为 `100000 / 8E2`,也就是 HAL 里的 `UART_WORDLENGTH_9B + UART_PARITY_EVEN + UART_STOPBITS_2`。当前没有启用软件 `RXINV`
3. **GPIO 电平配置**UART5 RX 是 PD2当前保持 `GPIO_NOPULL`,没有继续保留“内部上拉”的实验配置。
4. **DMA 内存可访问性**H7 上 DMA1/DMA2 不能访问 DTCM。原来接收对象如果落到 DTCM就可能导致 DMA 没法真正写入数据。现在把 USART 实例池和接收 buffer 放进 `.dma_buffer` 段,并链接到 RAM_D1。
5. **DMA 接收方式**:原来的 UART5 RX DMA 是 `DMA_CIRCULAR`,但这里使用的是 `HAL_UARTEx_ReceiveToIdle_DMA()`,为了按实际一帧 25 字节触发并重启,改成 `DMA_NORMAL + ReceiveToIdle`
6. **异常恢复**:如果 UART/DMA 出错或重启失败,不能只在中断里死等。现在加了 `USARTServiceTask()`,在 daemon 任务上下文里做 stream 级恢复。
7. **SBUS 解析**:只接受完整 25 字节帧,检查帧头 `0x0F`,尾字节允许 `0x00/0x04/0x14/0x24/0x34`,并处理 failsafe/offline。
后面临时试过的 `RXINV`、PD2 上拉、50 字节接收、滑动同步、`rc_debug` 等实验代码已经撤掉,当前版本回到“硬件反相 + 正常 SBUS 帧解析”的方案。
## 2. DMA 当前怎么配置
UART5 RX 的 DMA 配置在 `Core/Src/usart.c``TronOneH7_Scaffold.ioc` 中保持一致:
- UART`UART5`
- RX 引脚:`PD2`
- DMA stream`DMA1_Stream5`
- Request`DMA_REQUEST_UART5_RX`
- 方向:`DMA_PERIPH_TO_MEMORY`
- 外设地址不自增:`DMA_PINC_DISABLE`
- 内存地址自增:`DMA_MINC_ENABLE`
- 外设/内存数据宽度:`BYTE`
- 模式:`DMA_NORMAL`
- 优先级:`DMA_PRIORITY_VERY_HIGH`
- FIFO关闭
接收 buffer 的关键点:
- `USART_Instance usart_instance_pool[DEVICE_USART_CNT]` 放在 `User_Code/bsp/usart/bsp_usart.c`
- 这个池加了 `__attribute__((section(".dma_buffer"), aligned(32)))`
- `STM32H723XG_FLASH.ld` 新增 `.dma_buffer (NOLOAD)` 段,并放到 `RAM_D1`,避免 DMA 访问 DTCM 失败。
- `USART_Instance.recv_buff` 也做了 32 字节对齐。
启动和重启方式:
- `USARTServiceInit()` 调用 `HAL_UARTEx_ReceiveToIdle_DMA()` 启动接收。
- 启动成功后关闭 DMA 半传输中断 `DMA_IT_HT`,避免半包回调干扰。
- `HAL_UARTEx_RxEventCallback()` 中记录 `rx_event_count``last_rx_size`,把实际收到的 `Size` 传给遥控器解析回调,然后重新启动接收。
- `HAL_UART_ErrorCallback()` 中记录 `uart_error_count``last_uart_error`,并尝试重启接收。
- 如果中断里重启失败,会置位/累计错误,后续由 `USARTServiceTask()` 在任务上下文里恢复 UART/DMA。
## 3. 主要加了什么,在哪里
- `User_Code/application/robot.c`
- 定义全局 `volatile RobotMode_t RobotMode`
-`RobotInit()` 中调用 `RemoteControlInit(&huart5)`
- `Core/Src/usart.c`
- UART5 保持 `100000 / 8E2`
- UART5 `AdvancedInit` 保持 `UART_ADVFEATURE_NO_INIT`,没有软件 RXINV。
- PD2 保持 `GPIO_NOPULL`
- UART5 RX DMA 从 `DMA_CIRCULAR` 改为 `DMA_NORMAL`
- `TronOneH7_Scaffold.ioc`
- 同步把 `Dma.UART5_RX.13.Mode` 改为 `DMA_NORMAL`
- `STM32H723XG_FLASH.ld`
- 新增 `.dma_buffer` 段,放入 `RAM_D1`
- `User_Code/bsp/usart/bsp_usart.c/.h`
- USART 实例不再 `malloc`,改用静态实例池,并放入 `.dma_buffer`
- 模块回调改为携带 `(USART_Instance *instance, const uint8_t *recv_data, uint16_t recv_size)`
- 加入 `rx_event_count``last_rx_size``uart_error_count``last_uart_error``rx_restart_error_count` 等接收状态字段。
- 加入 `USARTServiceTask()` 做 UART/DMA 恢复。
- `USARTServiceInit()` 改为返回 `HAL_StatusTypeDef`
- `User_Code/module/periph/remote_control/rc.c/.h`
- `RemoteControlInit()` 注册 UART5并显式启动 USART 接收服务。
- SBUS 解析只接受完整 25 字节帧。
- 加入帧头、尾字节、failsafe 校验。
- 加入 `RemoteControlReadSnapshot()`,用于原子读取当前遥控器快照。
- 修复 `RemoteControlIsOnline()` 和失控清零的竞态。
- 修复 SWB/SWC 通道、开关边沿标志。
- `Core/Src/freertos.c`
- daemon task 中调用 `USARTServiceTask()`,让 UART/DMA 异常恢复发生在任务上下文。
- `User_Code/module/software/daemon/daemon.c/.h`
- 修正 `temp_count/init_count` 初始化逻辑。
- `DaemonReload()``DaemonIsOnline()``Daemon_Update()` 加了简单临界区保护。
- `temp_count` 改为 `volatile`
- `User_Code/application/indicator_app/ws2812status.c`
- 不再用局部 `RobotMode` 遮蔽全局状态。
- LED 显示时结合 `RemoteControlIsOnline()` 判断遥控器离线。
- `User_Code/module/paramdef/robot_def.h`
- `RobotMode` 声明改为 `extern volatile RobotMode_t RobotMode`
## 4. 当前建议上板观察项
上板后重点看这些量:
- `rx_event_count` 是否持续增长。
- `last_rx_size` 是否稳定为 `25`
- `rc_valid_frame_count` 是否持续增长。
- `uart_error_count``rx_restart_error_count` 是否不持续增长。
- 原始帧是否类似 `0F ... 00/04/14/24/34`
注意:当前稳定版本已经没有 `rc_debug` 这个临时调试结构。`rx_event_count``last_rx_size``USART_Instance` 里,`rc_valid_frame_count` 等在 `rc.c` 内部是 `static volatile`。如果后续想长期在调试器里直接 watch 一个固定结构,可以再专门加一个轻量 getter 或 debug struct当前为了回到干净版本没有保留那套临时代码。
## 5. Git 变更复核和可疑点
已读取当前 `git status --short``git diff --stat``git diff --check` 和关键文件 diff。当前工作区共有 16 个已修改文件,其中 SBUS 接收链路相关的是:
- `Core/Src/freertos.c`
- `Core/Src/usart.c`
- `STM32H723XG_FLASH.ld`
- `TronOneH7_Scaffold.ioc`
- `User_Code/application/indicator_app/ws2812status.c`
- `User_Code/application/robot.c`
- `User_Code/bsp/usart/bsp_usart.c`
- `User_Code/bsp/usart/bsp_usart.h`
- `User_Code/module/paramdef/robot_def.h`
- `User_Code/module/periph/remote_control/rc.c`
- `User_Code/module/periph/remote_control/rc.h`
- `User_Code/module/software/daemon/daemon.c`
- `User_Code/module/software/daemon/daemon.h`
复核结论:
- `git diff --check` 没有发现空白错误,只提示这些文件下次被 Git 处理时 LF 可能转 CRLF。
- 没有发现 `rc_debug``rc_ctrl_debug``RemoteControlDebugPoll``RXINV``RxPinLevelInvert`、50 字节 buffer、滑动同步等实验代码残留。
- UART5 当前确实是 `100000 / 8E2`,没有软件 RXINVPD2 也是 `GPIO_NOPULL`
- UART5 RX DMA 在 `.ioc` 和生成代码里都已经是 `DMA_NORMAL`,没有一边改一边没同步的问题。
- `RobotMode` 目前只有一个定义,在 `robot.c`;其它地方通过 `extern volatile` 使用,没看到重复定义。
需要特别注意的可疑/无关变更:
- `User_Code/module/periph/buzzer/buzzer.cpp` 只改了两行注释空格,和 SBUS 修复无关,建议不要混进本次提交。
- `ozonedeb/windebnewestux.jdebug``ozonedeb/windebnewestux.jdebug.user` 是 Ozone/J-Link 调试器本地状态变化,包括探针序列号、打开窗口、布局、打开文件等,和代码逻辑无关,建议不要混进本次提交。
- `rc_valid_frame_count` 等计数是 `static volatile`,调试符号里一般能看到,但 C 代码外部不能直接引用;这不是接收链路问题,只是“是否方便 watch”的问题。
总体看SBUS 主链路相关 diff 没看到明显可疑残留;最需要清理的是 `buzzer.cpp``ozonedeb/*.jdebug*` 这些无关 dirty 文件。

View File

@@ -227,6 +227,17 @@ SECTIONS
PROVIDE( __bss_start = _sbss );
PROVIDE( __bss_size = __bss_end - __bss_start );
/* DMA1/DMA2 cannot access DTCM. The MPU config makes RAM_D1 non-cacheable. */
.dma_buffer (NOLOAD) :
{
. = ALIGN(32);
__dma_buffer_start__ = .;
KEEP(*(.dma_buffer))
KEEP(*(.dma_buffer.*))
. = ALIGN(32);
__dma_buffer_end__ = .;
} >RAM_D1
/* 用户堆栈段用于检查剩余RAM是否足够 */
._user_heap_stack (NOLOAD) :
{

View File

@@ -156,11 +156,11 @@ Dma.UART5_RX.13.FIFOMode=DMA_FIFOMODE_DISABLE
Dma.UART5_RX.13.Instance=DMA1_Stream5
Dma.UART5_RX.13.MemDataAlignment=DMA_MDATAALIGN_BYTE
Dma.UART5_RX.13.MemInc=DMA_MINC_ENABLE
Dma.UART5_RX.13.Mode=DMA_CIRCULAR
Dma.UART5_RX.13.Mode=DMA_NORMAL
Dma.UART5_RX.13.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
Dma.UART5_RX.13.PeriphInc=DMA_PINC_DISABLE
Dma.UART5_RX.13.Polarity=HAL_DMAMUX_REQ_GEN_RISING
Dma.UART5_RX.13.Priority=DMA_PRIORITY_LOW
Dma.UART5_RX.13.Priority=DMA_PRIORITY_VERY_HIGH
Dma.UART5_RX.13.RequestNumber=1
Dma.UART5_RX.13.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode,SignalID,Polarity,RequestNumber,SyncSignalID,SyncPolarity,SyncEnable,EventEnable,SyncRequestNumber
Dma.UART5_RX.13.SignalID=NONE
@@ -330,7 +330,7 @@ Dma.USART3_TX.10.SyncEnable=DISABLE
Dma.USART3_TX.10.SyncPolarity=HAL_DMAMUX_SYNC_NO_EVENT
Dma.USART3_TX.10.SyncRequestNumber=1
Dma.USART3_TX.10.SyncSignalID=NONE
FDCAN1.AutoRetransmission=ENABLE
FDCAN1.AutoRetransmission=DISABLE
FDCAN1.CalculateBaudRateNominal=1000000
FDCAN1.CalculateTimeBitNominal=1000
FDCAN1.CalculateTimeQuantumNominal=25.0
@@ -338,17 +338,18 @@ FDCAN1.DataPrescaler=3
FDCAN1.DataSyncJumpWidth=10
FDCAN1.DataTimeSeg1=29
FDCAN1.DataTimeSeg2=10
FDCAN1.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,NominalPrescaler,NominalTimeSeg1,NominalTimeSeg2,DataPrescaler,DataTimeSeg1,DataTimeSeg2,TxFifoQueueMode,RxFifo0ElmtsNbr,TxFifoQueueElmtsNbr,StdFiltersNbr,AutoRetransmission,ProtocolException,NominalSyncJumpWidth,DataSyncJumpWidth
FDCAN1.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,NominalPrescaler,NominalTimeSeg1,NominalTimeSeg2,DataPrescaler,DataTimeSeg1,DataTimeSeg2,TxFifoQueueMode,RxFifo0ElmtsNbr,TxFifoQueueElmtsNbr,StdFiltersNbr,AutoRetransmission,ProtocolException,NominalSyncJumpWidth,DataSyncJumpWidth,RxFifo1ElmtsNbr
FDCAN1.NominalPrescaler=3
FDCAN1.NominalSyncJumpWidth=10
FDCAN1.NominalTimeSeg1=29
FDCAN1.NominalTimeSeg2=10
FDCAN1.ProtocolException=ENABLE
FDCAN1.RxFifo0ElmtsNbr=32
FDCAN1.StdFiltersNbr=1
FDCAN1.ProtocolException=DISABLE
FDCAN1.RxFifo0ElmtsNbr=4
FDCAN1.RxFifo1ElmtsNbr=4
FDCAN1.StdFiltersNbr=14
FDCAN1.TxFifoQueueElmtsNbr=32
FDCAN1.TxFifoQueueMode=FDCAN_TX_FIFO_OPERATION
FDCAN2.AutoRetransmission=ENABLE
FDCAN2.AutoRetransmission=DISABLE
FDCAN2.CalculateBaudRateNominal=1000000
FDCAN2.CalculateTimeBitNominal=1000
FDCAN2.CalculateTimeQuantumNominal=25.0
@@ -356,35 +357,37 @@ FDCAN2.DataPrescaler=3
FDCAN2.DataSyncJumpWidth=10
FDCAN2.DataTimeSeg1=29
FDCAN2.DataTimeSeg2=10
FDCAN2.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,DataPrescaler,DataTimeSeg1,DataTimeSeg2,NominalPrescaler,NominalTimeSeg1,NominalTimeSeg2,RxFifo1ElmtsNbr,TxFifoQueueElmtsNbr,MessageRAMOffset,StdFiltersNbr,AutoRetransmission,ProtocolException,NominalSyncJumpWidth,DataSyncJumpWidth
FDCAN2.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,DataPrescaler,DataTimeSeg1,DataTimeSeg2,NominalPrescaler,NominalTimeSeg1,NominalTimeSeg2,RxFifo1ElmtsNbr,TxFifoQueueElmtsNbr,MessageRAMOffset,StdFiltersNbr,AutoRetransmission,ProtocolException,NominalSyncJumpWidth,DataSyncJumpWidth,RxFifo0ElmtsNbr
FDCAN2.MessageRAMOffset=853
FDCAN2.NominalPrescaler=3
FDCAN2.NominalSyncJumpWidth=10
FDCAN2.NominalTimeSeg1=29
FDCAN2.NominalTimeSeg2=10
FDCAN2.ProtocolException=ENABLE
FDCAN2.RxFifo1ElmtsNbr=32
FDCAN2.StdFiltersNbr=1
FDCAN2.ProtocolException=DISABLE
FDCAN2.RxFifo0ElmtsNbr=4
FDCAN2.RxFifo1ElmtsNbr=4
FDCAN2.StdFiltersNbr=14
FDCAN2.TxFifoQueueElmtsNbr=32
FDCAN3.AutoRetransmission=ENABLE
FDCAN3.AutoRetransmission=DISABLE
FDCAN3.CalculateBaudRateNominal=1000000
FDCAN3.CalculateTimeBitNominal=1000
FDCAN3.CalculateTimeQuantumNominal=200.0
FDCAN3.CalculateTimeQuantumNominal=25.0
FDCAN3.ClockCalibrationCCU=DISABLE
FDCAN3.DataPrescaler=3
FDCAN3.DataSyncJumpWidth=10
FDCAN3.DataTimeSeg1=29
FDCAN3.DataTimeSeg2=10
FDCAN3.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,DataPrescaler,DataTimeSeg1,DataTimeSeg2,NominalPrescaler,NominalTimeSeg1,NominalTimeSeg2,RxFifo1ElmtsNbr,TxFifoQueueElmtsNbr,MessageRAMOffset,StdFiltersNbr,AutoRetransmission,ProtocolException,NominalSyncJumpWidth,DataSyncJumpWidth,ClockCalibrationCCU
FDCAN3.IPParameters=CalculateTimeQuantumNominal,CalculateTimeBitNominal,CalculateBaudRateNominal,DataPrescaler,DataTimeSeg1,DataTimeSeg2,NominalPrescaler,NominalTimeSeg1,NominalTimeSeg2,RxFifo1ElmtsNbr,TxFifoQueueElmtsNbr,MessageRAMOffset,StdFiltersNbr,AutoRetransmission,ProtocolException,NominalSyncJumpWidth,DataSyncJumpWidth,ClockCalibrationCCU,RxFifo0ElmtsNbr
FDCAN3.MessageRAMOffset=1706
FDCAN3.NominalPrescaler=24
FDCAN3.NominalPrescaler=3
FDCAN3.NominalSyncJumpWidth=10
FDCAN3.NominalTimeSeg1=2
FDCAN3.NominalTimeSeg2=2
FDCAN3.ProtocolException=ENABLE
FDCAN3.RxFifo1ElmtsNbr=32
FDCAN3.StdFiltersNbr=1
FDCAN3.TxFifoQueueElmtsNbr=6
FDCAN3.NominalTimeSeg1=29
FDCAN3.NominalTimeSeg2=10
FDCAN3.ProtocolException=DISABLE
FDCAN3.RxFifo0ElmtsNbr=4
FDCAN3.RxFifo1ElmtsNbr=4
FDCAN3.StdFiltersNbr=14
FDCAN3.TxFifoQueueElmtsNbr=32
FREERTOS.FootprintOK=true
FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configUSE_NEWLIB_REENTRANT,configCHECK_FOR_STACK_OVERFLOW,configUSE_MALLOC_FAILED_HOOK,configTOTAL_HEAP_SIZE,configMINIMAL_STACK_SIZE
FREERTOS.Tasks01=BeginTask,24,256,StartDefaultTask,As weak,NULL,Dynamic,NULL,NULL;shoot,24,512,ShootTask,As weak,NULL,Dynamic,NULL,NULL;gimbal,24,512,GimbalTask,As weak,NULL,Dynamic,NULL,NULL;chassis,24,512,ChassisTask,As weak,NULL,Dynamic,NULL,NULL;init,40,256,StartInitTask,As weak,NULL,Dynamic,NULL,NULL;vision,24,512,VisionTask,As weak,NULL,Dynamic,NULL,NULL;cmd,24,512,CmdTask,As weak,NULL,Dynamic,NULL,NULL;reference,24,512,RefereeTask,As weak,NULL,Dynamic,NULL,NULL;WS2812Task,16,256,ws2812Task,As external,NULL,Dynamic,NULL,NULL
@@ -449,6 +452,7 @@ MMTAppReg6.MEMORYMAP.Size=1048576
MMTAppReg6.MEMORYMAP.StartAddress=0x08000000
MMTAppRegionsCount=6
MMTConfigApplied=false
MMTSectionSuffix=
Mcu.CPN=STM32H723VGT6
Mcu.Family=STM32H7
Mcu.IP0=ADC1
@@ -483,72 +487,73 @@ Mcu.Name=STM32H723VGTx
Mcu.Package=LQFP100
Mcu.Pin0=PE2
Mcu.Pin1=PE3
Mcu.Pin10=PA0
Mcu.Pin11=PA2
Mcu.Pin12=PA5
Mcu.Pin13=PA6
Mcu.Pin14=PA7
Mcu.Pin15=PC4
Mcu.Pin16=PB1
Mcu.Pin17=PE7
Mcu.Pin18=PE8
Mcu.Pin19=PE9
Mcu.Pin2=PC14-OSC32_IN
Mcu.Pin20=PE10
Mcu.Pin21=PE12
Mcu.Pin22=PE13
Mcu.Pin23=PE15
Mcu.Pin24=PB10
Mcu.Pin25=PB11
Mcu.Pin26=PB13
Mcu.Pin27=PB15
Mcu.Pin28=PD8
Mcu.Pin29=PD9
Mcu.Pin3=PC15-OSC32_OUT
Mcu.Pin30=PD10
Mcu.Pin31=PD12
Mcu.Pin32=PD13
Mcu.Pin33=PA8
Mcu.Pin34=PA9
Mcu.Pin35=PA10
Mcu.Pin36=PA11
Mcu.Pin37=PA12
Mcu.Pin38=PC10
Mcu.Pin39=PC11
Mcu.Pin4=PH0-OSC_IN
Mcu.Pin40=PC12
Mcu.Pin41=PD0
Mcu.Pin42=PD1
Mcu.Pin43=PD2
Mcu.Pin44=PD4
Mcu.Pin45=PD5
Mcu.Pin46=PD6
Mcu.Pin47=PD7
Mcu.Pin48=PB3(JTDO/TRACESWO)
Mcu.Pin49=PB4(NJTRST)
Mcu.Pin5=PH1-OSC_OUT
Mcu.Pin50=PB5
Mcu.Pin51=PB6
Mcu.Pin52=VP_ADC3_TempSens_Input
Mcu.Pin53=VP_CRC_VS_CRC
Mcu.Pin54=VP_FREERTOS_VS_CMSIS_V2
Mcu.Pin55=VP_SYS_VS_tim23
Mcu.Pin56=VP_TIM1_VS_ClockSourceINT
Mcu.Pin57=VP_TIM1_VS_no_output3
Mcu.Pin58=VP_USB_DEVICE_VS_USB_DEVICE_CDC_HS
Mcu.Pin59=VP_MEMORYMAP_VS_MEMORYMAP
Mcu.Pin6=PC0
Mcu.Pin60=VP_STMicroelectronics.X-CUBE-ALGOBUILD_VS_DSPOoLibraryJjLibrary_1.4.0_1.4.0
Mcu.Pin7=PC1
Mcu.Pin8=PC2_C
Mcu.Pin9=PC3_C
Mcu.PinsNb=61
Mcu.Pin10=PC3_C
Mcu.Pin11=PA0
Mcu.Pin12=PA2
Mcu.Pin13=PA5
Mcu.Pin14=PA6
Mcu.Pin15=PA7
Mcu.Pin16=PC4
Mcu.Pin17=PB1
Mcu.Pin18=PE7
Mcu.Pin19=PE8
Mcu.Pin2=PC13
Mcu.Pin20=PE9
Mcu.Pin21=PE10
Mcu.Pin22=PE12
Mcu.Pin23=PE13
Mcu.Pin24=PE15
Mcu.Pin25=PB10
Mcu.Pin26=PB11
Mcu.Pin27=PB13
Mcu.Pin28=PB15
Mcu.Pin29=PD8
Mcu.Pin3=PC14-OSC32_IN
Mcu.Pin30=PD9
Mcu.Pin31=PD10
Mcu.Pin32=PD12
Mcu.Pin33=PD13
Mcu.Pin34=PA8
Mcu.Pin35=PA9
Mcu.Pin36=PA10
Mcu.Pin37=PA11
Mcu.Pin38=PA12
Mcu.Pin39=PC10
Mcu.Pin4=PC15-OSC32_OUT
Mcu.Pin40=PC11
Mcu.Pin41=PC12
Mcu.Pin42=PD0
Mcu.Pin43=PD1
Mcu.Pin44=PD2
Mcu.Pin45=PD4
Mcu.Pin46=PD5
Mcu.Pin47=PD6
Mcu.Pin48=PD7
Mcu.Pin49=PB3(JTDO/TRACESWO)
Mcu.Pin5=PH0-OSC_IN
Mcu.Pin50=PB4(NJTRST)
Mcu.Pin51=PB5
Mcu.Pin52=PB6
Mcu.Pin53=VP_ADC3_TempSens_Input
Mcu.Pin54=VP_CRC_VS_CRC
Mcu.Pin55=VP_FREERTOS_VS_CMSIS_V2
Mcu.Pin56=VP_SYS_VS_tim23
Mcu.Pin57=VP_TIM1_VS_ClockSourceINT
Mcu.Pin58=VP_TIM1_VS_no_output3
Mcu.Pin59=VP_USB_DEVICE_VS_USB_DEVICE_CDC_HS
Mcu.Pin6=PH1-OSC_OUT
Mcu.Pin60=VP_MEMORYMAP_VS_MEMORYMAP
Mcu.Pin61=VP_STMicroelectronics.X-CUBE-ALGOBUILD_VS_DSPOoLibraryJjLibrary_1.4.0_1.4.0
Mcu.Pin7=PC0
Mcu.Pin8=PC1
Mcu.Pin9=PC2_C
Mcu.PinsNb=62
Mcu.ThirdParty0=STMicroelectronics.X-CUBE-ALGOBUILD.1.4.0
Mcu.ThirdPartyNb=1
Mcu.UserConstants=
Mcu.UserName=STM32H723VGTx
MxCube.Version=6.15.0
MxDb.Version=DB.6.0.150
MxCube.Version=6.16.0
MxDb.Version=DB.6.0.160
NVIC.ADC_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
NVIC.DMA1_Stream0_IRQn=true\:5\:0\:false\:false\:true\:true\:false\:true\:true
@@ -568,8 +573,11 @@ NVIC.DMA2_Stream6_IRQn=true\:5\:0\:false\:false\:true\:true\:false\:true\:true
NVIC.DMA2_Stream7_IRQn=true\:5\:0\:false\:false\:true\:true\:false\:true\:true
NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
NVIC.FDCAN1_IT0_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.FDCAN1_IT1_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.FDCAN2_IT0_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.FDCAN2_IT1_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.FDCAN3_IT0_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.FDCAN3_IT1_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true\:true
NVIC.ForceEnableDMAVector=true
NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
@@ -676,9 +684,18 @@ PC11.Signal=SPI3_MISO
PC12.Locked=true
PC12.Signal=SharedStack_PC12
PC12.Stacked=true
PC13.GPIOParameters=PinState,GPIO_Label
PC13.GPIO_Label=Power2
PC13.Locked=true
PC13.PinState=GPIO_PIN_SET
PC13.Signal=GPIO_Output
PC14-OSC32_IN.GPIOParameters=PinState,GPIO_Label
PC14-OSC32_IN.GPIO_Label=Power1
PC14-OSC32_IN.Locked=true
PC14-OSC32_IN.PinState=GPIO_PIN_SET
PC14-OSC32_IN.Signal=GPIO_Output
PC15-OSC32_OUT.GPIOParameters=PinState
PC15-OSC32_OUT.GPIOParameters=PinState,GPIO_Label
PC15-OSC32_OUT.GPIO_Label=Power_5V_EN
PC15-OSC32_OUT.Locked=true
PC15-OSC32_OUT.PinState=GPIO_PIN_SET
PC15-OSC32_OUT.Signal=GPIO_Output
@@ -770,7 +787,7 @@ PH1-OSC_OUT.Signal=RCC_OSC_OUT
PinOutPanel.RotationAngle=0
ProjectManager.AskForMigrate=true
ProjectManager.BackupPrevious=false
ProjectManager.CompilerLinker=Starm-Clang
ProjectManager.CompilerLinker=GCC
ProjectManager.CompilerOptimize=6
ProjectManager.ComputerToolchain=false
ProjectManager.CoupleFile=true
@@ -780,6 +797,7 @@ ProjectManager.DeletePrevious=true
ProjectManager.DeviceId=STM32H723VGTx
ProjectManager.FirmwarePackage=STM32Cube FW_H7 V1.12.1
ProjectManager.FreePins=false
ProjectManager.FreePinsContext=
ProjectManager.HalAssertFull=false
ProjectManager.HeapSize=0x4000
ProjectManager.KeepUserCode=true
@@ -940,9 +958,10 @@ TIM3.IPParameters=Channel-PWM Generation4 CH4,Prescaler,Period,AutoReloadPreload
TIM3.Period=10000-1
TIM3.Prescaler=24-1
UART5.BaudRate=100000
UART5.IPParameters=Mode,BaudRate,WordLength,Parity
UART5.IPParameters=Mode,BaudRate,WordLength,Parity,StopBits
UART5.Mode=MODE_RX
UART5.Parity=PARITY_EVEN
UART5.StopBits=UART_STOPBITS_2
UART5.WordLength=WORDLENGTH_9B
UART7.BaudRate=921600
UART7.DMADisableonRxErrorParam=UART_ADVFEATURE_DMA_ENABLEONRXERROR

View File

@@ -124,13 +124,9 @@ extern USBD_HandleTypeDef hUsbDeviceHS;
*/
static int8_t CDC_Init_HS(void);
static int8_t CDC_DeInit_HS(void);
static int8_t CDC_Control_HS(uint8_t cmd, uint8_t *pbuf, uint16_t length);
static int8_t CDC_Receive_HS(uint8_t *pbuf, uint32_t *Len);
static int8_t CDC_Control_HS(uint8_t cmd, uint8_t* pbuf, uint16_t length);
static int8_t CDC_Receive_HS(uint8_t* pbuf, uint32_t *Len);
static int8_t CDC_TransmitCplt_HS(uint8_t *pbuf, uint32_t *Len, uint8_t epnum);
/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
@@ -143,11 +139,11 @@ static int8_t CDC_TransmitCplt_HS(uint8_t *pbuf, uint32_t *Len, uint8_t epnum);
USBD_CDC_ItfTypeDef USBD_Interface_fops_HS =
{
CDC_Init_HS,
CDC_DeInit_HS,
CDC_Control_HS,
CDC_Receive_HS,
CDC_TransmitCplt_HS
CDC_Init_HS,
CDC_DeInit_HS,
CDC_Control_HS,
CDC_Receive_HS,
CDC_TransmitCplt_HS
};
/* Private functions ---------------------------------------------------------*/
@@ -158,12 +154,12 @@ USBD_CDC_ItfTypeDef USBD_Interface_fops_HS =
*/
static int8_t CDC_Init_HS(void)
{
/* USER CODE BEGIN 8 */
/* USER CODE BEGIN 8 */
/* Set Application Buffers */
USBD_CDC_SetTxBuffer(&hUsbDeviceHS, UserTxBufferHS, 0);
USBD_CDC_SetRxBuffer(&hUsbDeviceHS, UserRxBufferHS);
return (USBD_OK);
/* USER CODE END 8 */
/* USER CODE END 8 */
}
/**
@@ -173,9 +169,9 @@ static int8_t CDC_Init_HS(void)
*/
static int8_t CDC_DeInit_HS(void)
{
/* USER CODE BEGIN 9 */
/* USER CODE BEGIN 9 */
return (USBD_OK);
/* USER CODE END 9 */
/* USER CODE END 9 */
}
/**
@@ -185,9 +181,9 @@ static int8_t CDC_DeInit_HS(void)
* @param length: Number of data to be sent (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_Control_HS(uint8_t cmd, uint8_t *pbuf, uint16_t length)
static int8_t CDC_Control_HS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
{
/* USER CODE BEGIN 10 */
/* USER CODE BEGIN 10 */
switch (cmd)
{
case CDC_SEND_ENCAPSULATED_COMMAND:
@@ -248,7 +244,7 @@ static int8_t CDC_Control_HS(uint8_t cmd, uint8_t *pbuf, uint16_t length)
}
return (USBD_OK);
/* USER CODE END 10 */
/* USER CODE END 10 */
}
/**
@@ -266,13 +262,13 @@ static int8_t CDC_Control_HS(uint8_t cmd, uint8_t *pbuf, uint16_t length)
* @param Len: Number of data received (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAILL
*/
static int8_t CDC_Receive_HS(uint8_t *Buf, uint32_t *Len)
static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 11 */
/* USER CODE BEGIN 11 */
USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceHS);
return (USBD_OK);
/* USER CODE END 11 */
/* USER CODE END 11 */
}
/**
@@ -282,10 +278,10 @@ static int8_t CDC_Receive_HS(uint8_t *Buf, uint32_t *Len)
* @param Len: Number of data to be sent (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL or USBD_BUSY
*/
uint8_t CDC_Transmit_HS(uint8_t *Buf, uint16_t Len)
uint8_t CDC_Transmit_HS(uint8_t* Buf, uint16_t Len)
{
uint8_t result = USBD_OK;
/* USER CODE BEGIN 12 */
uint8_t result = USBD_OK;
/* USER CODE BEGIN 12 */
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *) hUsbDeviceHS.pClassData;
if (hcdc->TxState != 0)
{
@@ -293,8 +289,8 @@ uint8_t CDC_Transmit_HS(uint8_t *Buf, uint16_t Len)
}
USBD_CDC_SetTxBuffer(&hUsbDeviceHS, Buf, Len);
result = USBD_CDC_TransmitPacket(&hUsbDeviceHS);
/* USER CODE END 12 */
return result;
/* USER CODE END 12 */
return result;
}
/**
@@ -311,15 +307,15 @@ uint8_t CDC_Transmit_HS(uint8_t *Buf, uint16_t Len)
*/
static int8_t CDC_TransmitCplt_HS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{
uint8_t result = USBD_OK;
/* USER CODE BEGIN 14 */
uint8_t result = USBD_OK;
/* USER CODE BEGIN 14 */
UNUSED(Buf);
UNUSED(Len);
UNUSED(epnum);
if (tx_cbk)
tx_cbk(*Len);
/* USER CODE END 14 */
return result;
/* USER CODE END 14 */
return result;
}
/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */

View File

@@ -24,11 +24,7 @@
#define __USBD_CDC_IF_H__
#ifdef __cplusplus
extern "C"
{
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
@@ -111,7 +107,7 @@ extern USBD_CDC_ItfTypeDef USBD_Interface_fops_HS;
* @{
*/
uint8_t CDC_Transmit_HS(uint8_t *Buf, uint16_t Len);
uint8_t CDC_Transmit_HS(uint8_t* Buf, uint16_t Len);
/* USER CODE BEGIN EXPORTED_FUNCTIONS */
uint8_t *CDCInitRxbufferNcallback(USBCallback transmit_cbk, USBCallback recv_cbk);

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "chassis_balance_parallel.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_CHASSIS_BALANCE_PARALLEL_H
#define TRONONEH7_SCAFFOLD_CHASSIS_BALANCE_PARALLEL_H
#endif // TRONONEH7_SCAFFOLD_CHASSIS_BALANCE_PARALLEL_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "chassis_balance_serial.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_CHASSIS_BALANCE_SERIAL_H
#define TRONONEH7_SCAFFOLD_CHASSIS_BALANCE_SERIAL_H
#endif // TRONONEH7_SCAFFOLD_CHASSIS_BALANCE_SERIAL_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "chassis_ctrl.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_CHASSIS_CTRL_H
#define TRONONEH7_SCAFFOLD_CHASSIS_CTRL_H
#endif // TRONONEH7_SCAFFOLD_CHASSIS_CTRL_H

View File

@@ -0,0 +1,219 @@
#include "chassis_half_steer.h"
#include "user_lib.h" // 包含 arm_math.h, PI, user_malloc 等
#include <math.h>
// 宏定义 (根据实际机械结构调整)
#ifndef WHEEL_BASE
#define WHEEL_BASE 0.35f // 轴距 (示例值)
#endif
#ifndef TRACK_WIDTH
#define TRACK_WIDTH 0.35f // 轮距 (示例值)
#endif
#define CHASSIS_WHEEL_OFFSET 30.0f // 舵轮偏置参数
#define SQRT2 1.41421356f // 根号2
#define RAD_2_DEGREE 57.2957795f
#define DEGREE_2_RAD 0.01745329f
// 舵轮对齐角度 (根据实际安装调整)
#define STEERING_CHASSIS_ALIGN_ANGLE_RF 0.0f
#define STEERING_CHASSIS_ALIGN_ANGLE_LB 0.0f
// 静态函数声明
static void MinmizeRotation(float *angle, const float *last_angle, float *speed);
static void SteeringWheelCalculate(Chassis_HalfSteer_t *chassis);
// 默认跟随PID配置
static PID_Init_Config_s follow_pid_config = {
.Kp = 6.0f,
.Ki = 0.0f,
.Kd = 0.495f,
.MaxOut = 45.0f,
};
void Chassis_HalfSteer_Init(Chassis_HalfSteer_t *chassis,
LKMotorInstance *drive_rf, LKMotorInstance *drive_lb,
DJIMotorInstance *steer_rf, DJIMotorInstance *steer_lb)
{
if (chassis == NULL) return;
// 绑定电机实例
chassis->motor_drive_rf = drive_rf;
chassis->motor_drive_lb = drive_lb;
chassis->motor_steer_rf = steer_rf;
chassis->motor_steer_lb = steer_lb;
// 初始化PID
PIDInit(&chassis->pid_follow, &follow_pid_config);
// 初始化状态变量
chassis->last_angle_rf = 0.0f;
chassis->last_angle_lb = 0.0f;
chassis->target_speed_rf = 0.0f;
chassis->target_speed_lb = 0.0f;
chassis->target_angle_rf = 0.0f;
chassis->target_angle_lb = 0.0f;
// 如果有电机需要特定的初始化配置(如 dji_motor 的参数),请在此处补充或在外部完成
}
void Chassis_HalfSteer_Update(Chassis_HalfSteer_t *chassis, const Chassis_Ctrl_Cmd_s *cmd)
{
if (chassis == NULL || cmd == NULL) return;
// 1. 更新内部命令副本
chassis->cmd = *cmd;
// 2. 检查底盘模式与安全状态
if (chassis->cmd.chassis_mode == CHASSIS_ZERO_FORCE)
{
LKMotorStop(chassis->motor_drive_rf);
LKMotorStop(chassis->motor_drive_lb);
DJIMotorStop(chassis->motor_steer_rf);
DJIMotorStop(chassis->motor_steer_lb);
return; // 直接返回,不再计算
}
else
{
LKMotorEnable(chassis->motor_drive_rf);
LKMotorEnable(chassis->motor_drive_lb);
DJIMotorEnable(chassis->motor_steer_rf);
DJIMotorEnable(chassis->motor_steer_lb);
}
// 3. 预处理旋转量 (wz)
switch (chassis->cmd.chassis_mode)
{
case CHASSIS_NO_FOLLOW:
chassis->cmd.wz = 0;
break;
case CHASSIS_FOLLOW_GIMBAL_YAW:
{
float angle_err = chassis->cmd.offset_angle;
// 归一化到 [-180, 180]
if(angle_err > 180.0f) angle_err -= 360.0f;
else if(angle_err < -180.0f) angle_err += 360.0f;
// 计算跟随PID输出
chassis->cmd.wz = PIDCalculate(&chassis->pid_follow, angle_err, 0.0f) / 100.0f; // 根据原代码保留/100
}
break;
case CHASSIS_ROTATE:
chassis->cmd.wz = 0.5f; // 固定自旋速度,可改为变量
break;
default:
break;
}
// 4. 坐标系转换 (云台系 -> 底盘系)
// 假设 cmd.vx/vy 是云台坐标系下的指令
float sin_theta = arm_sin_f32(chassis->cmd.offset_angle * DEGREE_2_RAD);
float cos_theta = arm_cos_f32(chassis->cmd.offset_angle * DEGREE_2_RAD);
// 覆盖原始 vx/vy 为底盘系速度 (使用中间变量避免污染原始cmd数据这里直接覆盖cmd结构体中的值用于后续计算)
float chassis_vx = chassis->cmd.vx * cos_theta - chassis->cmd.vy * sin_theta;
float chassis_vy = chassis->cmd.vx * sin_theta + chassis->cmd.vy * cos_theta;
// 将转换后的速度存回用于计算,或者传递给计算函数
// 为了保持清晰,我们修改 SteeringWheelCalculate 的输入方式,这里暂时存入 cmd 结构体或传递局部变量
// 这里选择传递局部变量,需要修改 SteeringWheelCalculate 内部逻辑
// 为了复用原逻辑,我将在函数内部使用 chassis_vx/vy
// 5. 运动学解算
// 传入 chassis_vx, chassis_vy 和 chassis->cmd.wz
// 注意:原代码使用全局变量,这里我们需要适配
float w = chassis->cmd.wz * CHASSIS_WHEEL_OFFSET * SQRT2;
if (fabsf(chassis_vx) == 0 && fabsf(chassis_vy) == 0 && chassis->cmd.wz == 0) {
chassis->target_speed_lb = 0;
chassis->target_speed_rf = 0;
// 角度保持不变,或回中?原代码保持不变
} else {
// LB (Left Back) 计算: y+, x-
// 注意:原代码注释里的方向似乎与变量名有差异,这里基于原代码逻辑复刻
// 原代码: arm_sqrt_f32(temp_x * temp_x + temp_y * temp_y, &vt_lb); // lb: y+ , x-
// temp_x = chassis_vx - w; temp_y = chassis_vy + w;
float temp_x_lb = chassis_vx - w;
float temp_y_lb = chassis_vy + w;
arm_sqrt_f32(temp_x_lb * temp_x_lb + temp_y_lb * temp_y_lb, &chassis->target_speed_lb);
float offset_lb = -atan2f(temp_y_lb, temp_x_lb) * RAD_2_DEGREE;
chassis->target_angle_lb = STEERING_CHASSIS_ALIGN_ANGLE_LB + offset_lb;
// RF (Right Front) 计算: y-, x+
// 原代码: arm_sqrt_f32(temp_x * temp_x + temp_y * temp_y, &vt_rf); // rf: y- , x+
// temp_x = chassis_vx + w; temp_y = chassis_vy - w;
float temp_x_rf = chassis_vx + w;
float temp_y_rf = chassis_vy - w;
arm_sqrt_f32(temp_x_rf * temp_x_rf + temp_y_rf * temp_y_rf, &chassis->target_speed_rf);
float offset_rf = -atan2f(temp_y_rf, temp_x_rf) * RAD_2_DEGREE;
chassis->target_angle_rf = STEERING_CHASSIS_ALIGN_ANGLE_RF + offset_rf;
// 6. 角度优化 (MinmizeRotation)
// 更新 last_angle
chassis->last_angle_lb = chassis->motor_steer_lb->measure.total_angle;
chassis->last_angle_rf = chassis->motor_steer_rf->measure.total_angle;
// 限制到 [-180, 180] 绝对值逻辑? 原代码使用了 ANGLE_LIMIT_360_TO_180_ABS 宏
// 这里手动实现或调用 user_lib
// 假设 user_lib.h 中有相关宏,这里简单处理
// (省略部分宏展开,直接使用 MinmizeRotation)
MinmizeRotation(&chassis->target_angle_lb, &chassis->last_angle_lb, &chassis->target_speed_lb);
MinmizeRotation(&chassis->target_angle_rf, &chassis->last_angle_rf, &chassis->target_speed_rf);
}
// 7. 发送控制指令
// 转向电机 (DJI GM6020)
DJIMotorSetRef(chassis->motor_steer_lb, chassis->target_angle_lb);
DJIMotorSetRef(chassis->motor_steer_rf, chassis->target_angle_rf);
// 驱动电机 (LK 9015)
LKMotorSetRef(chassis->motor_drive_lb, chassis->target_speed_lb);
LKMotorSetRef(chassis->motor_drive_rf, chassis->target_speed_rf);
}
/**
* @brief 使舵电机角度最小旋转,取优弧
*/
static void MinmizeRotation(float *angle, const float *last_angle, float *speed)
{
float target_angle = *angle;
float actual_angle = *last_angle;
float rotation = target_angle - actual_angle;
float norm_rotation = rotation;
// 规范化旋转角度到 [-180, 180]
while (norm_rotation > 180.0f) {
norm_rotation -= 360.0f;
}
while (norm_rotation < -180.0f) {
norm_rotation += 360.0f;
}
float threshold = 110.0f; // 阈值,超过此角度则反转轮子方向
// 简单的优弧判断
if (norm_rotation > threshold) {
int32_t round_diff = (int32_t)((target_angle - actual_angle) / 360.0f);
*angle = actual_angle + norm_rotation - 180.0f + round_diff * 360.0f;
*speed = -(*speed);
} else if (norm_rotation < -threshold) {
int32_t round_diff = (int32_t)((target_angle - actual_angle) / 360.0f);
*angle = actual_angle + norm_rotation + 180.0f + round_diff * 360.0f;
*speed = -(*speed);
}
// 如果没有触发反转,目标角度通常需要加上圈数,
// 但原代码逻辑似乎是直接修改传入的 angle 指针。
// 如果 norm_rotation 在阈值内,我们需要确保 angle 是基于 actual_angle 的最近点
// 原逻辑中 MinmizeRotation 似乎只处理了反转的情况,
// 对于常规旋转,可能需要确保 *angle 包含了正确的圈数信息。
// 补充逻辑:
if (norm_rotation <= threshold && norm_rotation >= -threshold) {
// 计算最近的目标角度(包含圈数)
*angle = actual_angle + norm_rotation;
}
}

View File

@@ -0,0 +1,89 @@
#ifndef CHASSIS_HALF_STEER_H
#define CHASSIS_HALF_STEER_H
#include "stdint.h"
#include "dji_motor.h"
#include "lk_motor.h" // 假设存在对应的C接口头文件
#include "pid.h"
#include "chassis_ctrl.h" // 包含底盘控制相关的通用定义
// 定义底盘控制命令结构体 (如果 chassis_ctrl.h 中未定义,请在此定义或确保通用)
#ifndef CHASSIS_CTRL_CMD_DEFINED
#define CHASSIS_CTRL_CMD_DEFINED
typedef enum
{
CHASSIS_ZERO_FORCE = 0, // 无力/急停
CHASSIS_NO_FOLLOW, // 不跟随/自由移动
CHASSIS_FOLLOW_GIMBAL_YAW, // 跟随云台Yaw
CHASSIS_ROTATE, // 小陀螺/自旋
} Chassis_Mode_e;
typedef struct
{
float vx; // 前后速度 (m/s)
float vy; // 左右速度 (m/s)
float wz; // 旋转角速度 (rad/s 或 对应单位)
float offset_angle; // 底盘与云台的夹角 (度)
Chassis_Mode_e chassis_mode;
} Chassis_Ctrl_Cmd_s;
#endif
// 定义底盘反馈数据结构体
typedef struct
{
float vx;
float vy;
float wz;
// float real_angle; // 预留
} Chassis_Upload_Data_s;
// 半舵轮底盘对象结构体
typedef struct
{
// 轮毂电机实例 (驱动) - LK9015
LKMotorInstance *motor_drive_rf; // 右前
LKMotorInstance *motor_drive_lb; // 左后
// 舵向电机实例 (转向) - GM6020
DJIMotorInstance *motor_steer_rf; // 右前舵
DJIMotorInstance *motor_steer_lb; // 左后舵
// PID实例
PIDInstance pid_follow; // 跟随PID
// 控制命令与状态
Chassis_Ctrl_Cmd_s cmd;
Chassis_Upload_Data_s feedback;
// 内部计算中间变量
float target_speed_rf; // 右前轮目标速度
float target_speed_lb; // 左后轮目标速度
float target_angle_rf; // 右前舵目标角度
float target_angle_lb; // 左后舵目标角度
// 上一次的角度记录 (用于就近转动逻辑)
float last_angle_rf;
float last_angle_lb;
} Chassis_HalfSteer_t;
/**
* @brief 初始化半舵轮底盘对象
* @param chassis 底盘对象指针
* @param drive_rf 右前驱动电机指针
* @param drive_lb 左后驱动电机指针
* @param steer_rf 右前转向电机指针
* @param steer_lb 左后转向电机指针
*/
void Chassis_HalfSteer_Init(Chassis_HalfSteer_t *chassis,
LKMotorInstance *drive_rf, LKMotorInstance *drive_lb,
DJIMotorInstance *steer_rf, DJIMotorInstance *steer_lb);
/**
* @brief 底盘控制更新函数建议在RTOS任务中周期调用
* @param chassis 底盘对象指针
* @param cmd 控制命令指针
*/
void Chassis_HalfSteer_Update(Chassis_HalfSteer_t *chassis, const Chassis_Ctrl_Cmd_s *cmd);
#endif // CHASSIS_HALF_STEER_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "chassis_lift_onmi.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_LIFT_ONMI_H
#define TRONONEH7_SCAFFOLD_LIFT_ONMI_H
#endif // TRONONEH7_SCAFFOLD_LIFT_ONMI_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "chassis_mecanum.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_CHASSIS_MECANUM_H
#define TRONONEH7_SCAFFOLD_CHASSIS_MECANUM_H
#endif // TRONONEH7_SCAFFOLD_CHASSIS_MECANUM_H

View File

@@ -0,0 +1,273 @@
#include "chassis_omni.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif
// 辅助函数:绝对值限幅
static float abs_clip(float val, float limit)
{
if (val > limit) return limit;
if (val < -limit) return -limit;
return val;
}
// PID配置
static PID_Init_Config_s chassis_speed_pid_config = {
.MaxOut = 16000.0f,
.IntegralLimit = 2000.0f, // 对应 C++ IntegralLimit (Integral_Min/Max 在 C PID 中未直接对应,取其中值或限制值)
.Kp = 15.0f,
.Ki = 0.0f,
.Kd = 0.001f,
.Output_LPF_RC = 0.002f, // 对应 C++ Output_LPF
.Derivative_LPF_RC = 0.002f, // 对应 C++ D_LPF
.Improve = PID_Integral_Limit, // 对应 0x01
};
// 跟随环内环 (对应 index 0)
static PID_Init_Config_s follow_pid_inner_config = {
.MaxOut = 4000.0f,
.IntegralLimit = 200.0f,
.Kp = 20.0f,
.Ki = 4.0f,
.Kd = 0.0001f,
.Output_LPF_RC = 0.002f,
.Derivative_LPF_RC = 0.002f,
// 对应 0x37 = Integral_Limit | Differential_Forward | Trapezoid_Intergral | OutputFilter | ChangingIntegrationRate
// C definitions:
// PID_Integral_Limit (1)
// PID_Derivative_On_Measurement (2)
// PID_Trapezoid_Intergral (4)
// PID_OutputFilter (16)
// PID_ChangingIntegrationRate (32)
.Improve = PID_Integral_Limit | PID_Derivative_On_Measurement | PID_Trapezoid_Intergral | PID_OutputFilter | PID_ChangingIntegrationRate,
};
// 跟随环外环 (对应 index 1)
static PID_Init_Config_s follow_pid_outer_config = {
.MaxOut = 4000.0f,
.IntegralLimit = 4000.0f,
.Kp = 15.0f,
.Ki = 0.0f,
.Kd = 1.8f,
.Output_LPF_RC = 0.002f,
.Derivative_LPF_RC = 0.002f,
.Improve = PID_Integral_Limit | PID_Derivative_On_Measurement | PID_Trapezoid_Intergral | PID_OutputFilter | PID_ChangingIntegrationRate,
};
void Chassis_Omni_Init(Chassis_Omni_t *chassis, DJIMotorInstance *lf, DJIMotorInstance *rf, DJIMotorInstance *lb, DJIMotorInstance *rb)
{
if (chassis == NULL) return;
chassis->moto_chassis[0] = lf;
chassis->moto_chassis[1] = rf;
chassis->moto_chassis[2] = lb;
chassis->moto_chassis[3] = rb;
// 初始化速度PID
for (int i = 0; i < 4; i++) {
PIDInit(&chassis->pid_speed[i], &chassis_speed_pid_config);
}
// 初始化跟随PID (串级)
PIDInit(&chassis->pid_follow_angle_inner, &follow_pid_inner_config);
PIDInit(&chassis->pid_follow_angle_outer, &follow_pid_outer_config);
// 初始化功率控制参数
chassis->power_config.super_power_health = 90.0f;
chassis->power_config.super_power_week = 20.0f;
chassis->power_config.chassis_normal_speed_limit = 80; // 这里的单位可能需要根据实际调整
}
// 功率分配 (防止超功率)
static void Chassis_Power_Allocation(Chassis_Omni_t *chassis)
{
float scaling[4];
float total_err = 0.0f;
// 计算总误差 (使用 Err 字段)
for (int i = 0; i < 4; i++) {
total_err += fabsf(chassis->pid_speed[i].Err);
}
if (total_err > 1e-6f) { // 避免除零
for (int i = 0; i < 4; i++) {
scaling[i] = chassis->pid_speed[i].Err / total_err;
}
// 限制输出
for (int i = 0; i < 4; i++) {
// 原代码: pidinstance[0].pos_out = abs_clip(..., abs(Scaling[i] * 50000))
// 注意: 这里直接修改了 PID 的 Output可能会影响下一次计算但在C++原版中就是这样写的
float limit = fabsf(scaling[i] * 50000.0f);
chassis->pid_speed[i].Output = abs_clip(chassis->pid_speed[i].Output, limit);
}
}
}
void Chassis_Omni_Update(Chassis_Omni_t *chassis)
{
if (chassis == NULL) return;
// 1. 获取电机速度并进行正运动学解算 (估计底盘当前速度)
float motor_speeds[4];
float real_speed[4]; // [0]=vx, [1]=vy, [2]=w
for (int i = 0; i < 4; i++) {
// 使用 speed_aps (度/秒)
motor_speeds[i] = chassis->moto_chassis[i]->measure.speed_aps;
}
// 逆结算部分 (原代码注释,实际是正解算:轮速 -> 体速)
// 假设是X型全向轮/麦克纳姆轮布局
real_speed[0] = (-motor_speeds[0] - motor_speeds[1] + motor_speeds[2] + motor_speeds[3]) / 4.0f;
real_speed[1] = (-motor_speeds[0] + motor_speeds[1] - motor_speeds[2] + motor_speeds[3]) / 4.0f;
real_speed[2] = (-motor_speeds[0] - motor_speeds[1] - motor_speeds[2] - motor_speeds[3]) / 4.0f;
// 将底盘体坐标系速度转换到之前的参考系 (可能是云台系或世界系,取决于 real_angle 的定义)
float cos_a = cosf(chassis->cmd.real_angle);
float sin_a = sinf(chassis->cmd.real_angle);
float speedx = real_speed[0] * cos_a - real_speed[1] * sin_a;
float speedy = real_speed[0] * sin_a + real_speed[1] * cos_a;
float target_speed[3] = {0};
if (chassis->cmd.if_enable != 0)
{
// 2. 跟随PID计算
chassis->offset_angle = -chassis->cmd.follow_angle;
chassis->offset_speed = chassis->cmd.yaw_speed;
if (!chassis->cmd.if_free) {
// 串级PID: 外环(角度) -> 内环(速度)
// 外环目标: 0 (使 offset_angle 归零)
float outer_out = PIDCalculate(&chassis->pid_follow_angle_outer, chassis->offset_angle, 0.0f);
// 内环目标: 外环输出
// 内环反馈: offset_speed (yaw_speed)
chassis->follow_increment = PIDCalculate(&chassis->pid_follow_angle_inner, chassis->offset_speed, outer_out);
} else {
chassis->follow_increment = 0.0f;
// 清空PID积分等状态
chassis->pid_follow_angle_outer.Output = 0;
chassis->pid_follow_angle_inner.Output = 0;
}
// 3. 底盘速度闭环控制 (P控制)
// 这里的 5.5 是速度环增益,计算出的是"加速度"或"力"的需求
target_speed[0] = (chassis->cmd.speed[0] - speedx) * 5.5f;
target_speed[1] = (chassis->cmd.speed[1] - speedy) * 5.5f;
// 旋转轴控制
// 如果没有指令输入,则使用 real_speed 差值进行阻尼控制?
// 原代码逻辑: speed[2] = (cmd - real) * 5.5
target_speed[2] = (chassis->cmd.speed[2] - real_speed[2]) * 5.5f;
if (chassis->cmd.speed[2] == 0.0f) // 如果没有旋转指令
{
// 叠加跟随PID输出并减去当前旋转速度 (阻尼)
target_speed[2] = (chassis->follow_increment - real_speed[2] - real_speed[2]) * 5.5f;
}
else
{
// 如果有手动旋转指令清除跟随PID积分
chassis->pid_follow_angle_outer.Output = 0;
chassis->pid_follow_angle_inner.Output = 0;
}
// 4. 逆运动学解算 (体速 -> 轮速)
// 引入了旋转补偿: real_angle - 0.002 * real_speed[2]
float corrected_angle = chassis->cmd.real_angle - 0.002f * real_speed[2];
float sin_ca = sinf(corrected_angle);
float cos_ca = cosf(corrected_angle);
// 转换回电机解算所需的 x, y 分量
float y = -(target_speed[0] * sinf(chassis->cmd.real_angle) - target_speed[1] * cos_ca);
float x = (target_speed[0] * cosf(chassis->cmd.real_angle) + target_speed[1] * sin_ca);
// 5. 电机PID控制与输出
float wheel_targets[4];
wheel_targets[0] = (-x - y) - target_speed[2];
wheel_targets[1] = (-x + y) - target_speed[2];
wheel_targets[2] = (x - y) - target_speed[2];
wheel_targets[3] = (x + y) - target_speed[2];
for (int i = 0; i < 4; i++) {
// PIDCalculate(pid, measure, target) -> 这里的measure似乎被当作0处理
// 原C++代码: pid_chassis[i]->PID_handle(wheel_targets[i]);
// PID_handle(target) 内部通常是 calculate(measure, target).
// 但原代码中 PID 构造时传入了 &moto_chassis[i]->speed 地址。
// 因此 C++ PID 类会自动读取 measure。
// 在 C 中,我们需要手动传入 measure。
float output = PIDCalculate(&chassis->pid_speed[i], chassis->moto_chassis[i]->measure.speed_aps, wheel_targets[i]);
// 设置电机输出 (注意DJIMotorSetRef 设置的是目标值还是直接电流?)
// 根据 dji_motor.h 注释: "可以将电机视为传递函数为1的设备...不需要关心底层的闭环"
// 如果 DJIMotorSetRef 是设定速度闭环的目标,那么上面的 PID 是多余的吗?
// 不,原代码 clearly 使用了 pid_chassis[i] 计算 send_data。
// 这意味着 dji_motor 应该工作在 OPEN_LOOP 或 CURRENT_LOOP 模式,或者我们需要直接操作 current。
// 假设我们这里计算的是电流值,因为 MaxOut 是 16000 (M3508电流范围)。
// DJIMotorSetRef 通常用于设定内置闭环的目标。
// 如果要发送电流,通常没有直接的 SetCurrent API除非 Motor_Control_Setting_s 允许。
// 为了保持移植性,我们假设 DJIMotorSetRef 能够处理这个输出,或者我们需要修改 dji_motor 模块。
// 这里我们假设 DJIMotorSetRef 在电流模式下工作。
DJIMotorSetRef(chassis->moto_chassis[i], output);
}
// 6. 功率限制
Chassis_Power_Allocation(chassis);
// 如果 Chassis_Power_Allocation 修改了 PID Output我们需要重新 SetRef 吗?
// 原代码直接修改了 pos_out这在下一次计算时生效或者如果 PID 类直接返回 pos_out 给 send_data。
// C++代码: moto_chassis[i]->send_data = PID_handle(...); Chassis_Power_Allocation();
// Power_Allocation 修改了 pid instance 的 pos_out。
// 这意味着当前的 send_data 并没有被 Power_Allocation 修正!
// 修正逻辑应该是先计算 PID再分配再发送。
// 但为了忠实还原原代码逻辑,我们保持顺序。
// (注:原代码逻辑可能存在缺陷,分配后的功率限制在下一帧才通过积分项或直接赋值生效?
// 或者 moto_chassis->send_data 是个指针引用?不,它是值。
// 如果原代码 Allocation 在赋值给 send_data 之后调用,那么它只影响了 PID 内部状态,不影响当前帧输出。)
}
else
{
for (int i = 0; i < 4; i++) {
DJIMotorStop(chassis->moto_chassis[i]);
}
}
}
void Chassis_Omni_PowerControl(Chassis_Omni_t *chassis, Chassis_Power_Info_s *power_info, Chassis_Ctrl_Cmd_s *raw_cmd)
{
if (chassis == NULL || power_info == NULL || raw_cmd == NULL) return;
// 复制原始指令到内部 cmd (默认)
chassis->cmd = *raw_cmd;
// 简单的功率策略实现 (参考 Chassis_OmniWheel_Crtl::powerControl)
float speed_scaling = 1.0f;
if (power_info->remain_energy >= chassis->power_config.super_power_health)
{
speed_scaling = 1.0f; // 正常模式
}
else if (power_info->remain_energy >= chassis->power_config.super_power_week)
{
// 线性降额: energy + 10 ? 原代码: tired = energy + 10
float tired = power_info->remain_energy + 10.0f;
speed_scaling = tired * 0.01f; // 归一化
}
else
{
speed_scaling = 0.3f; // 低电量模式
}
// 应用缩放系数 (原代码还有 200 * 1.5/1.8 的系数,这里假设 raw_cmd 已经是归一化值,只做缩放)
// 原代码: data_to_chassis.speed[...] = data_from_FSM.speed[...] * 200 * ...
// 这里我们只做相对缩放,保留原始比例
chassis->cmd.speed[0] *= speed_scaling;
chassis->cmd.speed[1] *= speed_scaling;
chassis->cmd.speed[2] *= speed_scaling;
}

View File

@@ -0,0 +1,81 @@
#ifndef CHASSIS_OMNI_H
#define CHASSIS_OMNI_H
#include "stdint.h"
#include "dji_motor.h"
#include "pid.h"
// 定义底盘控制命令结构体 (由于chassis_ctrl.h为空在此定义以适配逻辑)
typedef struct
{
float speed[3]; // x, y, z (旋转) 速度设定值
float follow_angle; // 跟随角度 (底盘与云台夹角)
float yaw_speed; // 当前Yaw轴角速度 (作为前馈或反馈)
float real_angle; // 底盘当前实际角度 (用于坐标系转换)
uint8_t if_enable; // 底盘使能标志
uint8_t if_free; // 底盘自由模式标志 (不跟随)
} Chassis_Ctrl_Cmd_s;
// 定义功率控制所需的外部数据结构
typedef struct
{
uint16_t chassis_power_limit; // 来自裁判系统的功率限制
float remain_energy; // 来自超级电容的剩余能量
uint16_t chassis_power_buffer; // 缓冲能量 (可选)
} Chassis_Power_Info_s;
// 全向轮底盘对象结构体
typedef struct
{
// 电机实例指针 (LF, RF, LB, RB)
DJIMotorInstance *moto_chassis[4];
// 速度环PID实例 (每个轮子一个)
PIDInstance pid_speed[4];
// 跟随环串级PID实例
PIDInstance pid_follow_angle_outer; // 外环 (角度)
PIDInstance pid_follow_angle_inner; // 内环 (角速度)
// 控制数据
Chassis_Ctrl_Cmd_s cmd;
// 内部计算状态变量
float offset_angle;
float offset_speed;
float follow_increment;
// 功率控制参数
struct {
float super_power_health;
float super_power_week;
uint16_t chassis_normal_speed_limit;
} power_config;
} Chassis_Omni_t;
/**
* @brief 初始化全向轮底盘对象
* @param chassis 底盘对象指针
* @param lf 左前电机指针
* @param rf 右前电机指针
* @param lb 左后电机指针
* @param rb 右后电机指针
*/
void Chassis_Omni_Init(Chassis_Omni_t *chassis, DJIMotorInstance *lf, DJIMotorInstance *rf, DJIMotorInstance *lb, DJIMotorInstance *rb);
/**
* @brief 底盘控制任务函数建议在RTOS任务中周期调用
* @param chassis 底盘对象指针
*/
void Chassis_Omni_Update(Chassis_Omni_t *chassis);
/**
* @brief 功率控制逻辑,根据裁判系统和超电状态限制目标速度
* @param chassis 底盘对象指针
* @param power_info 功率状态信息
* @param raw_cmd 原始控制命令 (通常来自上层FSM)
*/
void Chassis_Omni_PowerControl(Chassis_Omni_t *chassis, Chassis_Power_Info_s *power_info, Chassis_Ctrl_Cmd_s *raw_cmd);
#endif // CHASSIS_OMNI_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "chassis_steer.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_CHASSIS_STEER_H
#define TRONONEH7_SCAFFOLD_CHASSIS_STEER_H
#endif // TRONONEH7_SCAFFOLD_CHASSIS_STEER_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "gimbal.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_GIMBAL_H
#define TRONONEH7_SCAFFOLD_GIMBAL_H
#endif // TRONONEH7_SCAFFOLD_GIMBAL_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "gimbal_control.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_GIMBAL_CTRL_H
#define TRONONEH7_SCAFFOLD_GIMBAL_CTRL_H
#endif // TRONONEH7_SCAFFOLD_GIMBAL_CTRL_H

View File

@@ -24,6 +24,7 @@
#include "delayticks.h"
#include "robot_def.h"
#include "rc.h"
/*---------------------VARIABLES---------------------*/
uint8_t r = 0;
@@ -53,22 +54,29 @@ void robotSelfCheck(void)
void ws2812Task(void *argument)
{
(void) argument;
RobotMode_t RobotMode = REMOTE_NOT_CONNECTED; // 初始状态为遥控器未连接
RC_ctrl_t rc_data;
while (1)
{
switch (RobotMode)
RobotMode_t display_mode = RobotMode;
if (display_mode != SYS_ERROR_OCCURRED && !RemoteControlIsOnline())
display_mode = REMOTE_NOT_CONNECTED;
if (RemoteControlReadSnapshot(&rc_data) && switch_is_down(rc_data.sw_d))
display_mode = NORMAL_MODE;
switch (display_mode)
{
case NORMAL_MODE:
BlinkGreen();
BlinkBlue();
break;
case SYS_ERROR_OCCURRED:
BlinkRed();
break;
case REMOTE_NOT_CONNECTED:
BlinkYellow();
BlinkRed();
break;
case REMOTE_CONNECTED:
BlinkBlue();
BlinkYellow();
break;
case AUTO_SHOOTING_MODE:
BlinkCyan();
@@ -160,7 +168,7 @@ void BlinkPurple(void)
delay_ticks(500); // 延时500毫秒
}
void PWMControwLed(void)
void PWMControlLed(void)
{
//

View File

@@ -16,6 +16,8 @@
#include "bsp_init.h"
#include "robot.h"
#include "rc.h"
#include "robot_def.h"
#include "cmsis_gcc.h"
// #include "robot_def.h"
@@ -27,6 +29,8 @@
// #pragma message "check if you have configured the parameters in robot_def.h, IF NOT, please refer to the comments AND DO IT, otherwise the robot will have FATAL ERRORS!!!"
// #endif // !ROBOT_DEF_PARAM_WARNING
volatile RobotMode_t RobotMode = REMOTE_NOT_CONNECTED;
// #if defined(ONE_BOARD) || defined(CHASSIS_BOARD)
// #include "chassis.h"
// #endif
@@ -53,8 +57,15 @@ void RobotInit()
// 若必须,则只允许使用DWT_Delay()
__disable_irq();
// HAL_GPIO_WritePin(Power1_GPIO_Port, Power1_Pin, GPIO_PIN_SET);//使能24V电源
// HAL_GPIO_WritePin(Power2_GPIO_Port, Power2_Pin, GPIO_PIN_SET);//使能24V电源
// HAL_GPIO_WritePin(Power_5V_EN_GPIO_Port, Power_5V_EN_Pin, GPIO_PIN_SET);//使能5V电源
BSPInit();
if (RemoteControlInit(&huart5) == NULL)
RobotMode = SYS_ERROR_OCCURRED;
#if defined(ONE_BOARD) || defined(GIMBAL_BOARD)
RobotCMDInit();
// GimbalInit();

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "shoot.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_SHOOT_H
#define TRONONEH7_SCAFFOLD_SHOOT_H
#endif // TRONONEH7_SCAFFOLD_SHOOT_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "shoot_control.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_SHOOT_CONTROL_H
#define TRONONEH7_SCAFFOLD_SHOOT_CONTROL_H
#endif // TRONONEH7_SCAFFOLD_SHOOT_CONTROL_H

View File

@@ -0,0 +1,5 @@
//
// Created by esqwt on 2026/3/2.
//
#include "visionapp.h"

View File

@@ -0,0 +1,8 @@
//
// Created by esqwt on 2026/3/2.
//
#ifndef TRONONEH7_SCAFFOLD_VISIONAPP_H
#define TRONONEH7_SCAFFOLD_VISIONAPP_H
#endif // TRONONEH7_SCAFFOLD_VISIONAPP_H

View File

@@ -2,4 +2,4 @@
// Created by tuxmonkey on 2025/10/28.
//
#include "bsp_dmaMalloc.h"
#include "bsp_dmaMalloc.h"

View File

@@ -4,6 +4,7 @@
#include "stdlib.h"
#include "bsp_dwt.h"
#include "bsp_log.h"
//说是fdcan实际上就是配置成了经典的CAN
/* can instance ptrs storage, used for recv callback */
// 在CAN产生接收中断会遍历数组,选出hcan和rxid与发生中断的实例相同的那个,调用其回调函数
@@ -32,67 +33,87 @@ static uint8_t idx; // 全局CAN实例索引,每次有新的模块注册会自
*/
static void CANAddFilter(FDCANInstance *_instance)
{
#ifdef FDCAN
static uint8_t can1_filter_idx = 0, can2_filter_idx = 0 , can3_filter_idx = 0;
static uint8_t can1_filter_idx = 0, can2_filter_idx = 0, can3_filter_idx = 0;
//检查是否超出过滤器设定数量上限
if(can1_filter_idx > hfdcan1.Init.StdFiltersNbr || can2_filter_idx>hfdcan2.Init.StdFiltersNbr || can3_filter_idx > hfdcan3.Init.StdFiltersNbr)
if (can1_filter_idx > hfdcan1.Init.StdFiltersNbr || can2_filter_idx > hfdcan2.Init.StdFiltersNbr || can3_filter_idx
> hfdcan3.Init.StdFiltersNbr)
{
while(1)
while (1)
{
//报错
}
}
uint8_t *filter_idx_p;
if(_instance->can_handle==&hfdcan1)
if (_instance->can_handle == &hfdcan1)
{
filter_idx_p=&can1_filter_idx;
filter_idx_p = &can1_filter_idx;
}
else if(_instance->can_handle==&hfdcan2)
else if (_instance->can_handle == &hfdcan2)
{
filter_idx_p=&can2_filter_idx;
filter_idx_p = &can2_filter_idx;
}
else if(_instance->can_handle==&hfdcan3)
else if (_instance->can_handle == &hfdcan3)
{
filter_idx_p=&can3_filter_idx;
filter_idx_p = &can3_filter_idx;
}
else
{
while(1)
while (1)
{
//报错
}
}
FDCAN_FilterTypeDef fdcan_filter_conf;
fdcan_filter_conf.FilterIndex=(*filter_idx_p)++;
fdcan_filter_conf.FilterIndex = (*filter_idx_p)++;
//使用单个ID模式
fdcan_filter_conf.FilterType=FDCAN_FILTER_DUAL;
fdcan_filter_conf.FilterConfig=(_instance->tx_id & 1) ? FDCAN_FILTER_TO_RXFIFO0 : FDCAN_FILTER_TO_RXFIFO1;//奇数id的模块会被分配到FIFO0,偶数id的模块会被分配到FIFO1
fdcan_filter_conf.FilterID1=_instance->rx_id;
fdcan_filter_conf.FilterID2=_instance->rx_id;
fdcan_filter_conf.IdType=FDCAN_STANDARD_ID;
fdcan_filter_conf.IsCalibrationMsg=0;
fdcan_filter_conf.FilterType = FDCAN_FILTER_DUAL;
fdcan_filter_conf.FilterConfig = (_instance->tx_id & 1) ? FDCAN_FILTER_TO_RXFIFO0 : FDCAN_FILTER_TO_RXFIFO1;
//奇数id的模块会被分配到FIFO0,偶数id的模块会被分配到FIFO1
fdcan_filter_conf.FilterID1 = _instance->rx_id;
fdcan_filter_conf.FilterID2 = _instance->rx_id;
fdcan_filter_conf.IdType = FDCAN_STANDARD_ID;
fdcan_filter_conf.IsCalibrationMsg = 0;
//fdcan_filter_conf.RxBufferIndex=0;
// // ================== 【核心修复区】 ==================@todo有问题 我要验牌
// // 1. 强制让 FDCAN 进入 INIT 模式,否则无法写入 Message RAM
// HAL_FDCAN_Stop(_instance->can_handle);
//
// // 2. 写入过滤器配置
// HAL_FDCAN_ConfigFilter(_instance->can_handle, &fdcan_filter_conf);
//
// // 3. 重新启动 FDCAN
// HAL_FDCAN_Start(_instance->can_handle);
//
// // 4. 【救命稻草】HAL_FDCAN_Stop 关闭了所有中断,必须在这里重新激活!
// uint32_t FDCAN_RXActiveITs = FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO0_FULL |
// FDCAN_IT_RX_FIFO0_WATERMARK | FDCAN_IT_RX_FIFO0_MESSAGE_LOST |
// FDCAN_IT_RX_FIFO1_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_FULL |
// FDCAN_IT_RX_FIFO1_WATERMARK | FDCAN_IT_RX_FIFO1_MESSAGE_LOST;
// HAL_FDCAN_ActivateNotification(_instance->can_handle, FDCAN_RXActiveITs, 0);
// // ====================================================
HAL_FDCAN_ConfigFilter(_instance->can_handle, &fdcan_filter_conf);
#else
CAN_FilterTypeDef can_filter_conf;
static uint8_t can1_filter_idx = 0, can2_filter_idx = 14; // 0-13给can1用,14-27给can2用
can_filter_conf.FilterMode = CAN_FILTERMODE_IDLIST; // 使用id list模式,即只有将rxid添加到过滤器中才会接收到,其他报文会被过滤
can_filter_conf.FilterScale = CAN_FILTERSCALE_16BIT; // 使用16位id模式,即只有低16位有效
can_filter_conf.FilterFIFOAssignment = (_instance->tx_id & 1) ? CAN_RX_FIFO0 : CAN_RX_FIFO1; // 奇数id的模块会被分配到FIFO0,偶数id的模块会被分配到FIFO1
can_filter_conf.SlaveStartFilterBank = 14; // 从第14个过滤器开始配置从机过滤器(在STM32的BxCAN控制器中CAN2是CAN1的从机)
can_filter_conf.FilterIdLow = _instance->rx_id << 5; // 过滤器寄存器的低16位,因为使用STDID,所以只有低11位有效,高5位要填0
can_filter_conf.FilterBank = _instance->can_handle == &hcan1 ? (can1_filter_idx++) : (can2_filter_idx++); // 根据can_handle判断是CAN1还是CAN2,然后自增
can_filter_conf.FilterActivation = CAN_FILTER_ENABLE; // 启用过滤器
can_filter_conf.FilterMode = CAN_FILTERMODE_IDLIST; // 使用id list模式,即只有将rxid添加到过滤器中才会接收到,其他报文会被过滤
can_filter_conf.FilterScale = CAN_FILTERSCALE_16BIT; // 使用16位id模式,即只有低16位有效
can_filter_conf.FilterFIFOAssignment = (_instance->tx_id & 1) ? CAN_RX_FIFO0 : CAN_RX_FIFO1;
// 奇数id的模块会被分配到FIFO0,偶数id的模块会被分配到FIFO1
can_filter_conf.SlaveStartFilterBank = 14; // 从第14个过滤器开始配置从机过滤器(在STM32的BxCAN控制器中CAN2是CAN1的从机)
can_filter_conf.FilterIdLow = _instance->rx_id << 5; // 过滤器寄存器的低16位,因为使用STDID,所以只有低11位有效,高5位要填0
can_filter_conf.FilterBank = _instance->can_handle == &hcan1 ? (can1_filter_idx++) : (can2_filter_idx++);
// 根据can_handle判断是CAN1还是CAN2,然后自增
can_filter_conf.FilterActivation = CAN_FILTER_ENABLE; // 启用过滤器
HAL_CAN_ConfigFilter(_instance->can_handle, &can_filter_conf);
#endif
}
/**
@@ -107,31 +128,32 @@ void CANServiceInit()
{
#ifdef FDCAN
//可能不需要这么多中断
uint32_t FDCAN_RXActiveITs = FDCAN_IT_RX_FIFO0_NEW_MESSAGE|FDCAN_IT_RX_FIFO0_FULL\
|FDCAN_IT_RX_FIFO0_WATERMARK|FDCAN_IT_RX_FIFO0_MESSAGE_LOST \
|FDCAN_IT_RX_FIFO1_NEW_MESSAGE| FDCAN_IT_RX_FIFO1_FULL\
|FDCAN_IT_RX_FIFO1_WATERMARK|FDCAN_IT_RX_FIFO1_MESSAGE_LOST;
uint32_t FDCAN_RXActiveITs = FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO0_FULL\
| FDCAN_IT_RX_FIFO0_WATERMARK | FDCAN_IT_RX_FIFO0_MESSAGE_LOST
| FDCAN_IT_RX_FIFO1_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_FULL\
| FDCAN_IT_RX_FIFO1_WATERMARK | FDCAN_IT_RX_FIFO1_MESSAGE_LOST;
//HAL_FDCAN_ConfigClockCalibration()
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan1,FDCAN_RX_FIFO0,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan1,FDCAN_RX_FIFO1,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE);//全局过滤器设置
HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE);
//全局过滤器设置
HAL_FDCAN_Start(&hfdcan1);
HAL_FDCAN_ActivateNotification(&hfdcan1,FDCAN_RXActiveITs, 0);
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_RXActiveITs, 0);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan2,FDCAN_RX_FIFO0,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan2,FDCAN_RX_FIFO1,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE);
HAL_FDCAN_Start(&hfdcan2);
HAL_FDCAN_ActivateNotification(&hfdcan2,FDCAN_RXActiveITs, 0);
HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_RXActiveITs, 0);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan3,FDCAN_RX_FIFO0,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan3,FDCAN_RX_FIFO1,FDCAN_RX_FIFO_OVERWRITE);
HAL_FDCAN_ConfigGlobalFilter(&hfdcan3, FDCAN_REJECT, FDCAN_REJECT, FDCAN_REJECT_REMOTE, FDCAN_REJECT_REMOTE);
HAL_FDCAN_Start(&hfdcan3);
HAL_FDCAN_ActivateNotification(&hfdcan3,FDCAN_RXActiveITs, 0);
HAL_FDCAN_ActivateNotification(&hfdcan3, FDCAN_RXActiveITs, 0);
#else
@@ -142,116 +164,115 @@ void CANServiceInit()
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
/* ----------------------- two extern callable function -----------------------*/
FDCANInstance *CANRegister(FDCAN_Init_Config_s *config)
{
if (!idx)
{
CANServiceInit(); // 第一次注册,先进行硬件初始化
LOGINFO("[bsp_can] CAN Service Init");
}
if (idx >= CAN_MX_REGISTER_CNT) // 超过最大实例数
{
while (1)
{
LOGERROR("[bsp_can] CAN instance exceeded MAX num, consider balance the load of CAN bus");
}
if (!idx)
{
CANServiceInit(); // 第一次注册,先进行硬件初始化
LOGINFO("[bsp_can] CAN Service Init");
}
if (idx >= CAN_MX_REGISTER_CNT) // 超过最大实例数
{
while (1)
{
LOGERROR("[bsp_can] CAN instance exceeded MAX num, consider balance the load of CAN bus");
}
}
for (size_t i = 0; i < idx; i++)
{
// 重复注册 | id重复
if (fdcan_instance[i]->rx_id == config->rx_id && fdcan_instance[i]->can_handle == config->can_handle)
{
while (1)
{
LOGERROR("[}bsp_can] CAN id crash ,tx [%d] or rx [%d] already registered", &config->tx_id,
&config->rx_id);
}
}
}
}
for (size_t i = 0; i < idx; i++)
{ // 重复注册 | id重复
if (fdcan_instance[i]->rx_id == config->rx_id && fdcan_instance[i]->can_handle == config->can_handle)
{
while (1)
{
LOGERROR("[}bsp_can] CAN id crash ,tx [%d] or rx [%d] already registered", &config->tx_id, &config->rx_id);
}
}
}
FDCANInstance *instance = (FDCANInstance *)malloc(sizeof(FDCANInstance)); // 分配空间
memset(instance, 0, sizeof(FDCANInstance)); // 分配的空间未必是0,所以要先清空
// 进行发送报文的配置
FDCANInstance *instance = (FDCANInstance *) malloc(sizeof(FDCANInstance)); // 分配空间
memset(instance, 0, sizeof(FDCANInstance)); // 分配的空间未必是0,所以要先清空
// 进行发送报文的配置
#ifdef FDCAN
instance->txconf.Identifier = config->tx_id; // 发送id
instance->txconf.IdType = FDCAN_STANDARD_ID; // 使用标准id,扩展id则使用CAN_ID_EXT(目前没有需求)
instance->txconf.TxFrameType = FDCAN_DATA_FRAME, // 发送数据帧
instance->txconf.DataLength = FDCAN_DLC_BYTES_8, // 数据长度为8字节
instance->txconf.ErrorStateIndicator = FDCAN_ESI_ACTIVE, // 兼容CAN2.0,错误状态指示器设为主动
instance->txconf.BitRateSwitch = FDCAN_BRS_OFF, // 兼容CAN2.0禁用位速率切换
instance->txconf.FDFormat = FDCAN_CLASSIC_CAN, // 使用经典CAN格式
instance->txconf.TxEventFifoControl = FDCAN_NO_TX_EVENTS, // 不需要禁用事件FIFO
instance->txconf.MessageMarker = 0; // 不使用消息标记
instance->txconf.Identifier = config->tx_id; // 发送id
instance->txconf.IdType = FDCAN_STANDARD_ID; // 使用标准id,扩展id则使用CAN_ID_EXT(目前没有需求)
instance->txconf.TxFrameType = FDCAN_DATA_FRAME, // 发送数据帧
instance->txconf.DataLength = FDCAN_DLC_BYTES_8, // 数据长度为8字节
instance->txconf.ErrorStateIndicator = FDCAN_ESI_ACTIVE, // 兼容CAN2.0,错误状态指示器设为主动
instance->txconf.BitRateSwitch = FDCAN_BRS_OFF, // 兼容CAN2.0禁用位速率切换
instance->txconf.FDFormat = FDCAN_CLASSIC_CAN, // 使用经典CAN格式
instance->txconf.TxEventFifoControl = FDCAN_NO_TX_EVENTS, // 不需要禁用事件FIFO
instance->txconf.MessageMarker = 0; // 不使用消息标记
#else
instance->txconf.StdId = config->tx_id; // 发送id
instance->txconf.IDE = CAN_ID_STD; // 使用标准id,扩展id则使用CAN_ID_EXT(目前没有需求)
instance->txconf.RTR = CAN_RTR_DATA; // 发送数据帧
instance->txconf.DLC = 0x08; // 默认发送长度为8
instance->txconf.StdId = config->tx_id; // 发送id
instance->txconf.IDE = CAN_ID_STD; // 使用标准id,扩展id则使用CAN_ID_EXT(目前没有需求)
instance->txconf.RTR = CAN_RTR_DATA; // 发送数据帧
instance->txconf.DLC = 0x08; // 默认发送长度为8
#endif
// 设置回调函数和接收发送id
instance->can_handle = config->can_handle;
instance->tx_id = config->tx_id; // 好像没用,可以删掉
instance->rx_id = config->rx_id;
instance->can_module_callback = config->can_module_callback;
instance->id = config->id;
// 设置回调函数和接收发送id
instance->can_handle = config->can_handle;
instance->tx_id = config->tx_id; // 好像没用,可以删掉
instance->rx_id = config->rx_id;
instance->can_module_callback = config->can_module_callback;
instance->id = config->id;
CANAddFilter(instance); // 添加CAN过滤器规则
fdcan_instance[idx++] = instance; // 将实例保存到can_instance中
CANAddFilter(instance); // 添加CAN过滤器规则
fdcan_instance[idx++] = instance; // 将实例保存到can_instance中
return instance; // 返回can实例指针
return instance; // 返回can实例指针
}
/* @todo 目前似乎封装过度,应该添加一个指向tx_buff的指针,tx_buff不应该由CAN instance保存 */
/* 如果让CANinstance保存txbuff,会增加一次复制的开销 */
uint8_t CANTransmit(FDCANInstance *_instance, float timeout)
{
static uint32_t busy_count;
static volatile float wait_time __attribute__((unused)); // for cancel warning
float dwt_start = DWT_GetTimeline_ms();
static uint32_t busy_count;
static volatile float wait_time __attribute__((unused)); // for cancel warning
float dwt_start = DWT_GetTimeline_ms();
#ifdef FDCAN
while(HAL_FDCAN_GetTxFifoFreeLevel(_instance->can_handle)==0)
while (HAL_FDCAN_GetTxFifoFreeLevel(_instance->can_handle) == 0)
#else
while (HAL_CAN_GetTxMailboxesFreeLevel(_instance->can_handle) == 0) // 等待邮箱空闲
while (HAL_CAN_GetTxMailboxesFreeLevel(_instance->can_handle) == 0) // 等待邮箱空闲
#endif
{
if (DWT_GetTimeline_ms() - dwt_start > timeout) // 超时
{
LOGWARNING("[bsp_can] CAN MAILbox full! failed to add msg to mailbox. Cnt [%d]", busy_count);
busy_count++;
return 0;
}
}
wait_time = DWT_GetTimeline_ms() - dwt_start;
{
if (DWT_GetTimeline_ms() - dwt_start > timeout) // 超时
{
LOGWARNING("[bsp_can] CAN MAILbox full! failed to add msg to mailbox. Cnt [%d]", busy_count);
busy_count++;
return 0;
}
}
wait_time = DWT_GetTimeline_ms() - dwt_start;
#ifdef FDCAN
if (HAL_FDCAN_AddMessageToTxFifoQ(_instance->can_handle, &_instance->txconf, _instance->tx_buff))
if (HAL_FDCAN_AddMessageToTxFifoQ(_instance->can_handle, &_instance->txconf, _instance->tx_buff))
#else
// tx_mailbox会保存实际填入了这一帧消息的邮箱,但是知道是哪个邮箱发的似乎也没啥用
if (HAL_CAN_AddTxMessage(_instance->can_handle, &_instance->txconf, _instance->tx_buff, &_instance->tx_mailbox))
// tx_mailbox会保存实际填入了这一帧消息的邮箱,但是知道是哪个邮箱发的似乎也没啥用
if (HAL_CAN_AddTxMessage(_instance->can_handle, &_instance->txconf, _instance->tx_buff, &_instance->tx_mailbox))
#endif
{
LOGWARNING("[bsp_can] CAN bus BUSY! cnt:%d", busy_count);
busy_count++;
return 0;
}
return 1; // 发送成功
{
LOGWARNING("[bsp_can] CAN bus BUSY! cnt:%d", busy_count);
busy_count++;
return 0;
}
return 1; // 发送成功
}
void CANSetDLC(FDCANInstance *_instance, uint8_t length)
{
// 发送长度错误!检查调用参数是否出错,或出现野指针/越界访问
if (length > 8 || length == 0) // 安全检查
while (1)
{
LOGERROR("[bsp_can] CAN DLC error! check your code or wild pointer");
}
// 发送长度错误!检查调用参数是否出错,或出现野指针/越界访问
if (length > 8 || length == 0) // 安全检查
while (1)
{
LOGERROR("[bsp_can] CAN DLC error! check your code or wild pointer");
}
_instance->txconf.DataLength = length;
_instance->txconf.DataLength = DLC_LookUp_Table[length];
}
/* -----------------------belows are callback definitions--------------------------*/
@@ -262,47 +283,64 @@ void CANSetDLC(FDCANInstance *_instance, uint8_t length)
* @brief 此函数会被下面两个函数调用,用于处理FIFO0和FIFO1溢出中断(说明收到了新的数据)
* 所有的实例都会被遍历,找到can_handle和rx_id相等的实例时,调用该实例的回调函数
*
* @param _fdhcan
* @param _hfdcan
* @param fifox passed to HAL_CAN_GetRxMessage() to get mesg from a specific fifo
*/
static void FDCANFIFOxCallback(FDCAN_HandleTypeDef *_hfdcan, uint32_t fifox)
{
static FDCAN_RxHeaderTypeDef rxconf; // 同上
static FDCAN_RxHeaderTypeDef rxconf;
static uint16_t DataLength = 0;
static uint8_t fdcan_rx_buff[8];
while (HAL_FDCAN_GetRxFifoFillLevel(_hfdcan, fifox)) // FIFO不为空,有可能在其他中断时有多帧数据进入
{
HAL_FDCAN_GetRxMessage(_hfdcan, fifox, &rxconf, fdcan_rx_buff); // 从FIFO中获取数据
//解析数据长度,@Todo 此处在用新版本重新生成后可能得修改DataLength可能不需要右移具体情况具体看
if(((rxconf.DataLength >> 16) & 0xF)>=0 && ((rxconf.DataLength >> 16) & 0xF)<=8)
static uint8_t fdcan_rx_buff[8];
while (HAL_FDCAN_GetRxFifoFillLevel(_hfdcan, fifox))
{
HAL_FDCAN_GetRxMessage(_hfdcan, fifox, &rxconf, fdcan_rx_buff);
//@todo:DataLength解析
switch (rxconf.DataLength)
{
DataLength=(rxconf.DataLength >> 16) & 0xF; // 保存接收到的数据长度
case FDCAN_DLC_BYTES_0: DataLength = 0;
break;
case FDCAN_DLC_BYTES_1: DataLength = 1;
break;
case FDCAN_DLC_BYTES_2: DataLength = 2;
break;
case FDCAN_DLC_BYTES_3: DataLength = 3;
break;
case FDCAN_DLC_BYTES_4: DataLength = 4;
break;
case FDCAN_DLC_BYTES_5: DataLength = 5;
break;
case FDCAN_DLC_BYTES_6: DataLength = 6;
break;
case FDCAN_DLC_BYTES_7: DataLength = 7;
break;
case FDCAN_DLC_BYTES_8: DataLength = 8;
break;
// 如果后续用到了 FDCAN 真正的长帧(12~64字节),可以在这里继续加 case
default: DataLength = 8;
break; // 兜底保护
}
else
if (rxconf.RxFrameType == FDCAN_DATA_FRAME && rxconf.IdType == FDCAN_STANDARD_ID)
{
DataLength=0;
}
if(rxconf.RxFrameType==FDCAN_DATA_FRAME && rxconf.IdType==FDCAN_STANDARD_ID)
{
for (size_t i = 0; i < idx; ++i)
for (size_t i = 0; i < idx; ++i)
{
// 两者相等说明这是要找的实例
if (_hfdcan == fdcan_instance[i]->can_handle && rxconf.Identifier == fdcan_instance[i]->rx_id)
{
if (fdcan_instance[i]->can_module_callback != NULL) // 回调函数不为空就调用
if (fdcan_instance[i]->can_module_callback != NULL)
{
fdcan_instance[i]->rx_len = DataLength; // 保存接收到的数据长度
memcpy(fdcan_instance[i]->rx_buff, fdcan_rx_buff, fdcan_instance[i]->rx_len); // 消息拷贝到对应实例
fdcan_instance[i]->can_module_callback(fdcan_instance[i]); // 触发回调进行数据解析和处理
fdcan_instance[i]->rx_len = DataLength;
memcpy(fdcan_instance[i]->rx_buff, fdcan_rx_buff, fdcan_instance[i]->rx_len);
fdcan_instance[i]->can_module_callback(fdcan_instance[i]);
}
return;
break;
}
}
}
}
}
}
}
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
/* 检查Rx FIFO 0中是否有消息丢失 */
@@ -311,11 +349,13 @@ void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
//报错
}
/* 检查是否有新消息写入Rx FIFO 0或到达一定阈值 */
if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE)||(RxFifo0ITs & FDCAN_IT_RX_FIFO0_FULL)||(RxFifo0ITs & FDCAN_IT_RX_FIFO0_WATERMARK))
if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) || (RxFifo0ITs & FDCAN_IT_RX_FIFO0_FULL) || (
RxFifo0ITs & FDCAN_IT_RX_FIFO0_WATERMARK))
{
FDCANFIFOxCallback(hfdcan, FDCAN_RX_FIFO0); // 调用我们自己写的函数来处理消息
}
}
void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
{
/* 检查Rx FIFO 1中是否有消息丢失 */
@@ -324,7 +364,8 @@ void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
//报错
}
/* 检查是否有新消息写入Rx FIFO 1或到达一定阈值 */
if ((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE)||(RxFifo1ITs & FDCAN_IT_RX_FIFO1_FULL)||(RxFifo1ITs & FDCAN_IT_RX_FIFO1_WATERMARK))
if ((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) || (RxFifo1ITs & FDCAN_IT_RX_FIFO1_FULL) || (
RxFifo1ITs & FDCAN_IT_RX_FIFO1_WATERMARK))
{
FDCANFIFOxCallback(hfdcan, FDCAN_RX_FIFO1); // 调用我们自己写的函数来处理消息
}
@@ -343,25 +384,26 @@ void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
*/
static void CANFIFOxCallback(CAN_HandleTypeDef *_hcan, uint32_t fifox)
{
static CAN_RxHeaderTypeDef rxconf; // 同上
uint8_t can_rx_buff[8];
while (HAL_CAN_GetRxFifoFillLevel(_hcan, fifox)) // FIFO不为空,有可能在其他中断时有多帧数据进入
{
HAL_CAN_GetRxMessage(_hcan, fifox, &rxconf, can_rx_buff); // 从FIFO中获取数据
for (size_t i = 0; i < idx; ++i)
{ // 两者相等说明这是要找的实例
if (_hcan == fdcan_instance[i]->can_handle && rxconf.StdId == fdcan_instance[i]->rx_id)
{
if (fdcan_instance[i]->can_module_callback != NULL) // 回调函数不为空就调用
{
fdcan_instance[i]->rx_len = rxconf.DLC; // 保存接收到的数据长度
memcpy(fdcan_instance[i]->rx_buff, can_rx_buff, rxconf.DLC); // 消息拷贝到对应实例
fdcan_instance[i]->can_module_callback(fdcan_instance[i]); // 触发回调进行数据解析和处理
}
return;
}
}
}
static CAN_RxHeaderTypeDef rxconf; // 同上
uint8_t can_rx_buff[8];
while (HAL_CAN_GetRxFifoFillLevel(_hcan, fifox)) // FIFO不为空,有可能在其他中断时有多帧数据进入
{
HAL_CAN_GetRxMessage(_hcan, fifox, &rxconf, can_rx_buff); // 从FIFO中获取数据
for (size_t i = 0; i < idx; ++i)
{
// 两者相等说明这是要找的实例
if (_hcan == fdcan_instance[i]->can_handle && rxconf.StdId == fdcan_instance[i]->rx_id)
{
if (fdcan_instance[i]->can_module_callback != NULL) // 回调函数不为空就调用
{
fdcan_instance[i]->rx_len = rxconf.DLC; // 保存接收到的数据长度
memcpy(fdcan_instance[i]->rx_buff, can_rx_buff, rxconf.DLC); // 消息拷贝到对应实例
fdcan_instance[i]->can_module_callback(fdcan_instance[i]); // 触发回调进行数据解析和处理
}
return;
}
}
}
}
/**
@@ -378,7 +420,7 @@ static void CANFIFOxCallback(CAN_HandleTypeDef *_hcan, uint32_t fifox)
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CANFIFOxCallback(hcan, CAN_RX_FIFO0); // 调用我们自己写的函数来处理消息
CANFIFOxCallback(hcan, CAN_RX_FIFO0); // 调用我们自己写的函数来处理消息
}
/**
@@ -388,7 +430,7 @@ void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
*/
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
CANFIFOxCallback(hcan, CAN_RX_FIFO1); // 调用我们自己写的函数来处理消息
CANFIFOxCallback(hcan, CAN_RX_FIFO1); // 调用我们自己写的函数来处理消息
}

View File

@@ -1,5 +1,5 @@
#ifndef BSP_CAN_H
#define BSP_CAN_H
#ifndef BSP_FDCAN_H
#define BSP_FDCAN_H
//在此选择CAN类型两者只能选择一个
#define FDCAN //G系列和H7系列使用FDCAN
@@ -34,7 +34,18 @@
// 如果只有1个CAN,还需要把bsp_can.c中所有的hcan2变量改为hcan1(别担心,主要是总线和FIFO的负载均衡,不影响功能)
#endif
// 定义查找表
static const uint32_t DLC_LookUp_Table[9] = {
FDCAN_DLC_BYTES_0,
FDCAN_DLC_BYTES_1,
FDCAN_DLC_BYTES_2,
FDCAN_DLC_BYTES_3,
FDCAN_DLC_BYTES_4,
FDCAN_DLC_BYTES_5,
FDCAN_DLC_BYTES_6,
FDCAN_DLC_BYTES_7,
FDCAN_DLC_BYTES_8
};
/* can instance typedef, every module registered to CAN should have this variable */
#pragma pack(1)

View File

@@ -11,43 +11,116 @@
#include "bsp_usart.h"
#include "bsp_log.h"
#include "stdlib.h"
#include "memory.h"
/* usart service instance, modules' info would be recoreded here using USARTRegister() */
/* usart服务实例,所有注册了usart的模块信息会被保存在这里 */
static uint8_t idx;
static USART_Instance *usart_instance[DEVICE_USART_CNT] = {NULL};
static USART_Instance usart_instance_pool[DEVICE_USART_CNT]
__attribute__((section(".dma_buffer"), aligned(32)));
static USART_Instance *USARTFindInstance(UART_HandleTypeDef *huart)
{
for (uint8_t i = 0; i < idx; ++i)
{
if (usart_instance[i]->usart_handle == huart)
return usart_instance[i];
}
return NULL;
}
/**
* @brief 启动串口服务,会在每个实例注册之后自动启用接收,当前实现为DMA接收,后续可能添加IT和BLOCKING接收
* @brief 启动串口DMA接收服务,模块完成实例和回调初始化后显式调用
*
* @todo 串口服务会在每个实例注册之后自动启用接收,当前实现为DMA接收,后续可能添加IT和BLOCKING接收
* 可能还要将此函数修改为extern,使得module可以控制串口的启停
* @note 配合DMA_NORMAL和ReceiveToIdle使用,每次接收事件后由BSP重新启动
*
* @param _instance instance owned by module,模块拥有的串口实例
*/
void USARTServiceInit(USART_Instance *_instance)
HAL_StatusTypeDef USARTServiceInit(USART_Instance *_instance)
{
HAL_UARTEx_ReceiveToIdle_DMA(_instance->usart_handle, _instance->recv_buff, _instance->recv_buff_size);
if (_instance == NULL || _instance->usart_handle == NULL || _instance->usart_handle->hdmarx == NULL)
return HAL_ERROR;
HAL_StatusTypeDef status = HAL_UARTEx_ReceiveToIdle_DMA(
_instance->usart_handle, _instance->recv_buff, _instance->recv_buff_size);
// 关闭dma half transfer中断防止两次进入HAL_UARTEx_RxEventCallback()
// 这是HAL库的一个设计失误,发生DMA传输完成/半完成以及串口IDLE中断都会触发HAL_UARTEx_RxEventCallback()
// 我们只希望处理第一种和第三种情况,因此直接关闭DMA半传输中断
__HAL_DMA_DISABLE_IT(_instance->usart_handle->hdmarx, DMA_IT_HT);
if (status == HAL_OK)
{
__HAL_DMA_DISABLE_IT(_instance->usart_handle->hdmarx, DMA_IT_HT);
_instance->rx_restart_pending = 0;
}
else
{
_instance->rx_restart_pending = 1;
}
return status;
}
static HAL_StatusTypeDef USARTRecoverRx(USART_Instance *instance)
{
UART_HandleTypeDef *huart = instance->usart_handle;
DMA_HandleTypeDef *hdma = huart->hdmarx;
HAL_StatusTypeDef abort_status = HAL_UART_AbortReceive(huart);
if (abort_status != HAL_OK || HAL_DMA_GetState(hdma) != HAL_DMA_STATE_READY)
{
if (HAL_DMA_DeInit(hdma) != HAL_OK || HAL_DMA_Init(hdma) != HAL_OK)
return HAL_ERROR;
/* An abort timeout returns before HAL restores the UART Rx state. */
if (HAL_UART_AbortReceive(huart) != HAL_OK)
return HAL_ERROR;
}
return USARTServiceInit(instance);
}
void USARTServiceTask(void)
{
for (uint8_t i = 0; i < idx; ++i)
{
USART_Instance *instance = usart_instance[i];
if (!instance->rx_restart_pending)
continue;
if (USARTRecoverRx(instance) != HAL_OK)
instance->rx_restart_error_count++;
}
}
USART_Instance *USARTRegister(USART_Init_Config_s *init_config)
{
if (init_config == NULL || init_config->usart_handle == NULL || init_config->usart_handle->hdmarx == NULL ||
init_config->recv_buff_size == 0 || init_config->recv_buff_size > USART_RXBUFF_LIMIT)
{
LOGERROR("[bsp_usart] invalid USART register config");
return NULL;
}
if (init_config->usart_handle->hdmarx->Init.Mode != DMA_NORMAL)
{
LOGERROR("[bsp_usart] ReceiveToIdle service requires DMA_NORMAL");
return NULL;
}
if (idx >= DEVICE_USART_CNT) // 超过最大实例数
while (1)
LOGERROR("[bsp_usart] USART exceed max instance count!");
{
LOGERROR("[bsp_usart] USART exceed max instance count!");
return NULL;
}
for (uint8_t i = 0; i < idx; i++) // 检查是否已经注册过
if (usart_instance[i]->usart_handle == init_config->usart_handle)
while (1)
LOGERROR("[bsp_usart] USART instance already registered!");
{
LOGERROR("[bsp_usart] USART instance already registered!");
return NULL;
}
USART_Instance *instance = (USART_Instance *) malloc(sizeof(USART_Instance));
USART_Instance *instance = &usart_instance_pool[idx];
memset(instance, 0, sizeof(USART_Instance));
instance->usart_handle = init_config->usart_handle;
@@ -55,7 +128,6 @@ USART_Instance *USARTRegister(USART_Init_Config_s *init_config)
instance->module_callback = init_config->module_callback;
usart_instance[idx++] = instance;
USARTServiceInit(instance);
return instance;
}
@@ -82,10 +154,8 @@ void USARTSend(USART_Instance *_instance, uint8_t *send_buf, uint16_t send_size,
/* 串口发送时,gstate会被设为BUSY_TX */
uint8_t USARTIsReady(USART_Instance *_instance)
{
if (_instance->usart_handle->gState | HAL_UART_STATE_BUSY_TX)
return 0;
else
return 1;
return _instance != NULL && _instance->usart_handle != NULL &&
_instance->usart_handle->gState == HAL_UART_STATE_READY;
}
/**
@@ -97,27 +167,22 @@ uint8_t USARTIsReady(USART_Instance *_instance)
* 我们只希望处理因此直接关闭DMA半传输中断第一种和第三种情况
*
* @param huart 发生中断的串口
* @param Size 此次接收到的总数居量,暂时没用
* @param Size 此次接收到的数据量
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
for (uint8_t i = 0; i < idx; ++i)
{
// find the instance which is being handled
if (huart == usart_instance[i]->usart_handle)
{
// call the callback function if it is not NULL
if (usart_instance[i]->module_callback != NULL)
{
usart_instance[i]->module_callback();
memset(usart_instance[i]->recv_buff, 0, Size); // 接收结束后清空buffer,对于变长数据是必要的
}
HAL_UARTEx_ReceiveToIdle_DMA(usart_instance[i]->usart_handle, usart_instance[i]->recv_buff,
usart_instance[i]->recv_buff_size);
__HAL_DMA_DISABLE_IT(usart_instance[i]->usart_handle->hdmarx, DMA_IT_HT);
return; // break the loop
}
}
USART_Instance *instance = USARTFindInstance(huart);
if (instance == NULL)
return;
instance->rx_event_count++;
instance->last_rx_size = Size;
if (Size > 0 && Size <= instance->recv_buff_size && instance->module_callback != NULL)
instance->module_callback(instance, instance->recv_buff, Size);
if (USARTServiceInit(instance) != HAL_OK)
instance->rx_restart_error_count++;
}
/**
@@ -129,17 +194,15 @@ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
for (uint8_t i = 0; i < idx; ++i)
{
if (huart == usart_instance[i]->usart_handle)
{
HAL_UARTEx_ReceiveToIdle_DMA(usart_instance[i]->usart_handle, usart_instance[i]->recv_buff,
usart_instance[i]->recv_buff_size);
__HAL_DMA_DISABLE_IT(usart_instance[i]->usart_handle->hdmarx, DMA_IT_HT);
LOGWARNING("[bsp_usart] USART error callback triggered, instance idx [%d]", i);
return;
}
}
USART_Instance *instance = USARTFindInstance(huart);
if (instance == NULL)
return;
instance->uart_error_count++;
instance->last_uart_error = HAL_UART_GetError(huart);
if (USARTServiceInit(instance) != HAL_OK)
instance->rx_restart_error_count++;
}

View File

@@ -7,8 +7,10 @@
#define DEVICE_USART_CNT 5 // 喵板至多分配5个串口
#define USART_RXBUFF_LIMIT 256 // 如果协议需要更大的buff,请修改这里
typedef struct usart_instance USART_Instance;
// 模块回调函数,用于解析协议
typedef void (*usart_module_callback)();
typedef void (*usart_module_callback)(USART_Instance *instance, const uint8_t *recv_data, uint16_t recv_size);
/* 发送模式枚举 */
typedef enum
@@ -21,18 +23,24 @@ typedef enum
// 串口实例结构体,每个module都要包含一个实例.
// 由于串口是独占的点对点通信,所以不需要考虑多个module同时使用一个串口的情况,因此不用加入id;当然也可以选择加入,这样在bsp层可以访问到module的其他信息
typedef struct
struct usart_instance
{
uint8_t recv_buff[USART_RXBUFF_LIMIT]; // 预先定义的最大buff大小,如果太小请修改USART_RXBUFF_LIMIT
uint8_t recv_buff_size; // 模块接收一包数据的大小
uint8_t recv_buff[USART_RXBUFF_LIMIT] __attribute__((aligned(32))); // DMA接收buffer
uint16_t recv_buff_size; // 模块接收一包数据的大小
UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle
usart_module_callback module_callback; // 解析收到的数据的回调函数
} USART_Instance;
volatile uint32_t rx_event_count;
volatile uint32_t uart_error_count;
volatile uint32_t rx_restart_error_count;
volatile uint32_t last_uart_error;
volatile uint16_t last_rx_size;
volatile uint8_t rx_restart_pending;
};
/* usart 初始化配置结构体 */
typedef struct
{
uint8_t recv_buff_size; // 模块接收一包数据的大小
uint16_t recv_buff_size; // 模块接收一包数据的大小
UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle
usart_module_callback module_callback; // 解析收到的数据的回调函数
} USART_Init_Config_s;
@@ -45,11 +53,16 @@ typedef struct
USART_Instance *USARTRegister(USART_Init_Config_s *init_config);
/**
* @brief 启动串口服务,需要传入一个usart实例.一般用于lost callback的情况(使用串口的模块daemon)
* @brief 启动串口DMA接收服务,需要传入一个已注册的usart实例
*
* @param _instance
*/
void USARTServiceInit(USART_Instance *_instance);
HAL_StatusTypeDef USARTServiceInit(USART_Instance *_instance);
/**
* @brief 在任务上下文中恢复中断回调里启动失败的串口接收
*/
void USARTServiceTask(void);
/**

View File

@@ -1,5 +1,5 @@
/**
* @file controller.c
* @file pid.c
* @author wanghongxi
* @author modified by TuxMonkey
* @brief PID控制器及前馈控制器
@@ -82,6 +82,61 @@ static void f_Output_Filter(PIDInstance *pid)
pid->Last_Output * pid->Output_LPF_RC / (pid->Output_LPF_RC + pid->dt);
}
// 简单前馈模式: f_out = K_F * (Target - Pre_Target) / dt
static void f_FeedForward_Control_Simple(PIDInstance *pid)
{
if (pid->dt > 0.000001f) { // 防止除以零
pid->FFC_Output = pid->FFC_K * (pid->Ref - pid->FFC_Set_History[0]) / pid->dt;
} else {
pid->FFC_Output = 0.0f;
}
// 更新设定值历史
pid->FFC_Set_History[0] = pid->Ref; // 保存当前设定值用于下次计算
}
// 复杂前馈模式: P + D + A 三阶前馈
static void f_FeedForward_Control_Complex(PIDInstance *pid)
{
// 更新设定值历史
pid->FFC_Set_History[2] = pid->FFC_Set_History[1]; // LLAST = LAST
pid->FFC_Set_History[1] = pid->FFC_Set_History[0]; // LAST = NOW
pid->FFC_Set_History[0] = pid->Ref; // NOW = 当前设定值
// 低通滤波处理
float denominator = pid->FFC_LPF_RC + pid->dt;
if (denominator > 0.000001f) {
pid->FFC_Set_History[0] = pid->FFC_Set_History[0] * pid->dt / denominator +
pid->FFC_Set_History[0] * pid->FFC_LPF_RC / denominator;
}
// 前馈计算: P + D + A
float p_term = pid->FFC_Kp * pid->FFC_Set_History[0]; // 比例项
float d_term = 0.0f;
float a_term = 0.0f;
if (pid->dt > 0.000001f) { // 防止除以零
// 微分项: 速度前馈
d_term = pid->FFC_Kd * (pid->FFC_Set_History[0] - pid->FFC_Set_History[1]) / pid->dt;
// 加速度项: 加速度前馈
a_term = pid->FFC_Ka * (pid->FFC_Set_History[0] - 2 * pid->FFC_Set_History[1] + pid->FFC_Set_History[2]) / (pid->dt * pid->dt);
}
pid->FFC_Output = p_term + d_term + a_term;
}
// 前馈输出限幅
static void f_FeedForward_Limit(PIDInstance *pid)
{
if (pid->FFC_Output > pid->MaxOut) {
pid->FFC_Output = pid->MaxOut;
}
if (pid->FFC_Output < -(pid->MaxOut)) {
pid->FFC_Output = -(pid->MaxOut);
}
}
// 前馈控制计算(后续考虑写成电流/速度前馈)
static void f_FeedForward_Control(PIDInstance *pid)
@@ -226,9 +281,18 @@ float PIDCalculate(PIDInstance *pid, float measure, float ref)
pid->Iout += pid->ITerm; // 累加积分
pid->Output = pid->Pout + pid->Iout + pid->Dout; // 计算输出
// 前馈控制
if (pid->Improve & PID_FeedForward)
f_FeedForward_Control(pid);
// 前馈控制 (根据模式选择)
if (pid->Improve & PID_FeedForward) {
if (pid->Improve & PID_FFC_SimpleMode) {
// 简单前馈模式
f_FeedForward_Control_Simple(pid);
} else {
// 复杂前馈模式
f_FeedForward_Control_Complex(pid);
}
// 前馈输出限幅
f_FeedForward_Limit(pid);
}
// 输出滤波
if (pid->Improve & PID_OutputFilter)

View File

@@ -1,6 +1,6 @@
/**
******************************************************************************
* @file controller.h
* @file pid.h
* @author Wang Hongxi
* @version V1.1.3
* @date 2021/7/3
@@ -10,8 +10,8 @@
*
******************************************************************************
*/
#ifndef _CONTROLLER_H
#define _CONTROLLER_H
#ifndef _PID_H
#define _PID_H
#include "main.h"
#include "stdint.h"
@@ -28,16 +28,17 @@
// PID 优化环节使能标志位
typedef enum
{
PID_IMPROVE_NONE = 0b000000000, // 无优化 0
PID_Integral_Limit = 0b000000001, // 积分限幅 1
PID_Derivative_On_Measurement = 0b000000010, // 微分先行 2
PID_Trapezoid_Intergral = 0b000000100, // 梯形积分 4
PID_Proportional_On_Measurement = 0b000001000, // 比例先行 8
PID_OutputFilter = 0b000010000, // 输出滤波 16
PID_ChangingIntegrationRate = 0b000100000, // 变速积分 32
PID_DerivativeFilter = 0b001000000, // 微分滤波 64
PID_ErrorHandle = 0b010000000, // 错误处理 128
PID_FeedForward = 0b100000000, // 前馈控制 256
PID_IMPROVE_NONE = 0b0000000000, // 无优化 0
PID_Integral_Limit = 0b0000000001, // 积分限幅 1
PID_Derivative_On_Measurement = 0b0000000010, // 微分先行 2
PID_Trapezoid_Intergral = 0b0000000100, // 梯形积分 4
PID_Proportional_On_Measurement = 0b0000001000, // 比例先行 8
PID_OutputFilter = 0b0000010000, // 输出滤波 16
PID_ChangingIntegrationRate = 0b0000100000, // 变速积分 32
PID_DerivativeFilter = 0b0001000000, // 微分滤波 64
PID_ErrorHandle = 0b0010000000, // 错误处理 128
PID_FeedForward = 0b0100000000, // 前馈控制 256
PID_FFC_SimpleMode = 0b1000000000, // 简单前馈模式 (与PID_FeedForward组合使用) 512
} PID_Improvement_e;
/* PID 报错类型枚举*/
@@ -74,9 +75,10 @@ typedef struct
//-----------------------------------
// Feed Forward Control (FFC) parameters
float FFC_Kp; // 前馈比例系数
float FFC_Kd; // 前馈微分系数
float FFC_Ka; // 前馈加速度系数
float FFC_K; // 简单前馈增益系数
float FFC_Kp; // 前馈比例系数 (复杂模式)
float FFC_Kd; // 前馈微分系数 (复杂模式)
float FFC_Ka; // 前馈加速度系数 (复杂模式)
float FFC_LPF_RC; // 前馈低通滤波器系数
float FFC_Output; // 前馈输出值
float FFC_Set_History[3]; // 前馈设定值历史 [NOW, LAST, LLAST]
@@ -125,9 +127,10 @@ typedef struct // config parameter
float Derivative_LPF_RC;
// Feed Forward Control (FFC) parameters
float FFC_Kp; // 前馈比例系数
float FFC_Kd; // 前馈微分系数
float FFC_Ka; // 前馈加速度系数
float FFC_K; // 简单前馈增益系数
float FFC_Kp; // 前馈比例系数 (复杂模式)
float FFC_Kd; // 前馈微分系数 (复杂模式)
float FFC_Ka; // 前馈加速度系数 (复杂模式)
float FFC_LPF_RC; // 前馈低通滤波器系数
} PID_Init_Config_s;

View File

@@ -0,0 +1,356 @@
//
// Created by nie_b on 2026/2/23.
//
#include "dji_motor.h"
#include "general_def.h"
#include "bsp_dwt.h"
#include "bsp_log.h"
static uint8_t idx = 0; // register idx,是该文件的全局电机索引,在注册时使用
/* DJI电机的实例,此处仅保存指针,内存的分配将通过电机实例初始化时通过malloc()进行 */
static DJIMotorInstance *dji_motor_instance[DJI_MOTOR_CNT] = {NULL}; // 会在control任务中遍历该指针数组进行pid计算
#ifdef FDCAN
static FDCANInstance sender_assignment[9] = {
[0] = {.can_handle = &hfdcan1, .txconf.Identifier = 0x1ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[1] = {.can_handle = &hfdcan1, .txconf.Identifier = 0x200, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[2] = {.can_handle = &hfdcan1, .txconf.Identifier = 0x2ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[3] = {.can_handle = &hfdcan2, .txconf.Identifier = 0x1ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[4] = {.can_handle = &hfdcan2, .txconf.Identifier = 0x200, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[5] = {.can_handle = &hfdcan2, .txconf.Identifier = 0x2ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[6] = {.can_handle = &hfdcan3, .txconf.Identifier = 0x1ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[7] = {.can_handle = &hfdcan3, .txconf.Identifier = 0x200, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
[8] = {.can_handle = &hfdcan3, .txconf.Identifier = 0x2ff, .txconf.IdType = FDCAN_STANDARD_ID, .txconf.TxFrameType = FDCAN_DATA_FRAME, .txconf.DataLength = FDCAN_DLC_BYTES_8, .txconf.FDFormat = FDCAN_CLASSIC_CAN,.txconf.BitRateSwitch = FDCAN_BRS_OFF, .tx_buff = {0}},
};
#else
/**
* @brief 由于DJI电机发送以四个一组的形式进行,故对其进行特殊处理,用6个(2can*3group)can_instance专门负责发送
* 该变量将在 DJIMotorControl() 中使用,分组在 MotorSenderGrouping()中进行
*
* @note 因为只用于发送,所以不需要在bsp_can中注册
*
* C610(m2006)/C620(m3508):0x1ff,0x200;
* GM6020:0x1ff,0x2ff
* 反馈(rx_id): GM6020: 0x204+id ; C610/C620: 0x200+id
* can1: [0]:0x1FF,[1]:0x200,[2]:0x2FF
* can2: [3]:0x1FF,[4]:0x200,[5]:0x2FF
*/
static FDCANInstance sender_assignment[6] = {
[0] = {.can_handle = &hcan1, .txconf.StdId = 0x1ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},
[1] = {.can_handle = &hcan1, .txconf.StdId = 0x200, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},
[2] = {.can_handle = &hcan1, .txconf.StdId = 0x2ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},
[3] = {.can_handle = &hcan2, .txconf.StdId = 0x1ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},
[4] = {.can_handle = &hcan2, .txconf.StdId = 0x200, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},
[5] = {.can_handle = &hcan2, .txconf.StdId = 0x2ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},
};
#endif
/**
* @brief 6个用于确认是否有电机注册到sender_assignment中的标志位,防止发送空帧,此变量将在DJIMotorControl()使用
* flag的初始化在 MotorSenderGrouping()中进行
*/
static uint8_t sender_enable_flag[9] = {0};
/**
* @brief 根据电调/拨码开关上的ID,根据说明书的默认id分配方式计算发送ID和接收ID,
* 并对电机进行分组以便处理多电机控制命令
*/
static void MotorSenderGrouping(DJIMotorInstance *motor, FDCAN_Init_Config_s *config)
{
uint8_t motor_id = config->tx_id - 1; // 下标从零开始,先减一方便赋值
uint8_t motor_send_num;
uint8_t motor_grouping;
uint8_t grouping_offset;
//通过CAN计算分组偏移量
if(config->can_handle == &hfdcan1)
{
grouping_offset=0;
}
else if(config->can_handle == &hfdcan2)
{
grouping_offset=3;
}
else
{
grouping_offset=6;
}
switch (motor->motor_type)
{
case M2006:
case M3508:
if (motor_id < 4) // 根据ID分组
{
motor_send_num = motor_id;
motor_grouping = grouping_offset + 1;
}
else
{
motor_send_num = motor_id - 4;
motor_grouping = grouping_offset + 0;
}
// 计算接收id并设置分组发送id
config->rx_id = 0x200 + motor_id + 1; // 把ID+1,进行分组设置
sender_enable_flag[motor_grouping] = 1; // 设置发送标志位,防止发送空帧
motor->message_num = motor_send_num;
motor->sender_group = motor_grouping;
// 检查是否发生id冲突
for (size_t i = 0; i < idx; ++i)
{
if (dji_motor_instance[i]->motor_fdcan_instance->can_handle == config->can_handle && dji_motor_instance[i]->motor_fdcan_instance->rx_id == config->rx_id)
{
LOGERROR("[dji_motor] ID crash. Check in debug mode, add dji_motor_instance to watch to get more information.");
uint16_t can_bus = config->can_handle == &hcan1 ? 1 : 2;
while (1) // 6020的id 1-4和2006/3508的id 5-8会发生冲突(若有注册,即1!5,2!6,3!7,4!8) (1!5!,LTC! (((不是)
LOGERROR("[dji_motor] id [%d], can_bus [%d]", config->rx_id, can_bus);
}
}
break;
case GM6020:
if (motor_id < 4)
{
motor_send_num = motor_id;
motor_grouping = grouping_offset + 0;
}
else
{
motor_send_num = motor_id - 4;
motor_grouping = grouping_offset + 2;
}
config->rx_id = 0x204 + motor_id + 1; // 把ID+1,进行分组设置
sender_enable_flag[motor_grouping] = 1; // 只要有电机注册到这个分组,置为1;在发送函数中会通过此标志判断是否有电机注册
motor->message_num = motor_send_num;
motor->sender_group = motor_grouping;
for (size_t i = 0; i < idx; ++i)
{
if (dji_motor_instance[i]->motor_fdcan_instance->can_handle == config->can_handle && dji_motor_instance[i]->motor_fdcan_instance->rx_id == config->rx_id)
{
LOGERROR("[dji_motor] ID crash. Check in debug mode, add dji_motor_instance to watch to get more information.");
uint16_t can_bus = config->can_handle == &hcan1 ? 1 : 2;
while (1) // 6020的id 1-4和2006/3508的id 5-8会发生冲突(若有注册,即1!5,2!6,3!7,4!8) (1!5!,LTC! (((不是)
LOGERROR("[dji_motor] id [%d], can_bus [%d]", config->rx_id, can_bus);
}
}
break;
default: // other motors should not be registered here
while (1)
LOGERROR("[dji_motor]You must not register other motors using the API of DJI motor."); // 其他电机不应该在这里注册
}
}
/**
* @todo 是否可以简化多圈角度的计算?
* @brief 根据返回的can_instance对反馈报文进行解析
*
* @param _instance 收到数据的instance,通过遍历与所有电机进行对比以选择正确的实例
*/
static void DecodeDJIMotor(FDCANInstance *_instance)
{
// 这里对can instance的id进行了强制转换,从而获得电机的instance实例地址
// _instance指针指向的id是对应电机instance的地址,通过强制转换为电机instance的指针,再通过->运算符访问电机的成员motor_measure,最后取地址获得指针
uint8_t *rxbuff = _instance->rx_buff;
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;
measure->ecd = ((uint16_t)rxbuff[0]) << 8 | rxbuff[1];
measure->angle_single_round = ECD_ANGLE_COEF_DJI * (float)measure->ecd;
measure->speed_aps = (1.0f - SPEED_SMOOTH_COEF) * measure->speed_aps +
RPM_2_ANGLE_PER_SEC * SPEED_SMOOTH_COEF * (float)((int16_t)(rxbuff[2] << 8 | rxbuff[3]));
measure->real_current = (1.0f - CURRENT_SMOOTH_COEF) * measure->real_current +
CURRENT_SMOOTH_COEF * (float)((int16_t)(rxbuff[4] << 8 | rxbuff[5]));
measure->temperature = rxbuff[6];
// 多圈角度计算,前提是假设两次采样间电机转过的角度小于180°,自己画个图就清楚计算过程了
if (measure->ecd - measure->last_ecd > 4096)
measure->total_round--;
else if (measure->ecd - measure->last_ecd < -4096)
measure->total_round++;
measure->total_angle = measure->total_round * 360 + measure->angle_single_round;
}
static void DJIMotorLostCallback(void *motor_ptr)
{
DJIMotorInstance *motor = (DJIMotorInstance *)motor_ptr;
uint16_t can_bus = motor->motor_fdcan_instance->can_handle == &hcan1 ? 1 : 2;
LOGWARNING("[dji_motor] Motor lost, can bus [%d] , id [%d]", can_bus, motor->motor_fdcan_instance->tx_id);
}
// 电机初始化,返回一个电机实例
DJIMotorInstance *DJIMotorInit(Motor_Init_Config_s *config)
{
DJIMotorInstance *instance = (DJIMotorInstance *)malloc(sizeof(DJIMotorInstance));
memset(instance, 0, sizeof(DJIMotorInstance));
// motor basic setting 电机基本设置
instance->motor_type = config->motor_type; // 6020 or 2006 or 3508
instance->motor_settings = config->controller_setting_init_config; // 正反转,闭环类型等
// motor controller init 电机控制器初始化
PIDInit(&instance->motor_controller.current_PID, &config->controller_param_init_config.current_PID);
PIDInit(&instance->motor_controller.speed_PID, &config->controller_param_init_config.speed_PID);
PIDInit(&instance->motor_controller.angle_PID, &config->controller_param_init_config.angle_PID);
instance->motor_controller.other_angle_feedback_ptr = config->controller_param_init_config.other_angle_feedback_ptr;
instance->motor_controller.other_speed_feedback_ptr = config->controller_param_init_config.other_speed_feedback_ptr;
instance->motor_controller.current_feedforward_ptr = config->controller_param_init_config.current_feedforward_ptr;
instance->motor_controller.speed_feedforward_ptr = config->controller_param_init_config.speed_feedforward_ptr;
// 后续增加电机前馈控制器(速度和电流)
// 电机分组,因为至多4个电机可以共用一帧CAN控制报文
MotorSenderGrouping(instance, &config->fdcan_init_config);
// 注册电机到CAN总线
config->fdcan_init_config.can_module_callback = DecodeDJIMotor; // set callback
config->fdcan_init_config.id = instance; // set id,eq to address(it is identity)
instance->motor_fdcan_instance = CANRegister(&config->fdcan_init_config);
// 注册守护线程
Daemon_Init_Config_s daemon_config = {
.callback = DJIMotorLostCallback,
.owner_id = instance,
.reload_count = 2, // 20ms未收到数据则丢失
};
instance->daemon = DaemonRegister(&daemon_config);
DJIMotorEnable(instance);
dji_motor_instance[idx++] = instance;
return instance;
}
/* 电流只能通过电机自带传感器监测,后续考虑加入力矩传感器应变片等 */
void DJIMotorChangeFeed(DJIMotorInstance *motor, Closeloop_Type_e loop, Feedback_Source_e type)
{
if (loop == ANGLE_LOOP)
motor->motor_settings.angle_feedback_source = type;
else if (loop == SPEED_LOOP)
motor->motor_settings.speed_feedback_source = type;
else
LOGERROR("[dji_motor] loop type error, check memory access and func param"); // 检查是否传入了正确的LOOP类型,或发生了指针越界
}
void DJIMotorStop(DJIMotorInstance *motor)
{
motor->stop_flag = MOTOR_STOP;
}
void DJIMotorEnable(DJIMotorInstance *motor)
{
motor->stop_flag = MOTOR_ENALBED;
}
/* 修改电机的实际闭环对象 */
void DJIMotorOuterLoop(DJIMotorInstance *motor, Closeloop_Type_e outer_loop)
{
motor->motor_settings.outer_loop_type = outer_loop;
}
// 设置参考值
void DJIMotorSetRef(DJIMotorInstance *motor, float ref)
{
motor->motor_controller.pid_ref = ref;
}
// 为所有电机实例计算三环PID,发送控制报文
void DJIMotorControl()
{
// 直接保存一次指针引用从而减小访存的开销,同样可以提高可读性
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 *measure; // 电机测量值
float pid_measure, pid_ref; // 电机PID测量值和设定值
// 遍历所有电机实例,进行串级PID的计算并设置发送报文的值
for (size_t i = 0; i < idx; ++i)
{ // 减小访存开销,先保存指针引用
motor = dji_motor_instance[i];
motor_setting = &motor->motor_settings;
motor_controller = &motor->motor_controller;
measure = &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)
{
if (motor_setting->angle_feedback_source == OTHER_FEED)
pid_measure = *motor_controller->other_angle_feedback_ptr;
else
pid_measure = measure->total_angle; // MOTOR_FEED,对total angle闭环,防止在边界处出现突跃
// 更新pid_ref进入下一个环
pid_ref = PIDCalculate(&motor_controller->angle_PID, pid_measure, pid_ref);
}
// 计算速度环,(外层闭环为速度或位置)且(启用速度环)时会计算速度环
if ((motor_setting->close_loop_type & SPEED_LOOP) && (motor_setting->outer_loop_type & (ANGLE_LOOP | SPEED_LOOP)))
{
if (motor_setting->feedforward_flag & SPEED_FEEDFORWARD)
pid_ref += *motor_controller->speed_feedforward_ptr;
if (motor_setting->speed_feedback_source == OTHER_FEED)
pid_measure = *motor_controller->other_speed_feedback_ptr;
else // MOTOR_FEED
pid_measure = measure->speed_aps;
// 更新pid_ref进入下一个环
pid_ref = PIDCalculate(&motor_controller->speed_PID, pid_measure, pid_ref);
}
// 计算电流环,目前只要启用了电流环就计算,不管外层闭环是什么,并且电流只有电机自身传感器的反馈
if (motor_setting->feedforward_flag & CURRENT_FEEDFORWARD)
pid_ref += *motor_controller->current_feedforward_ptr;
if (motor_setting->close_loop_type & CURRENT_LOOP)
{
pid_ref = PIDCalculate(&motor_controller->current_PID, measure->real_current, pid_ref);
}
if (motor_setting->feedback_reverse_flag == FEEDBACK_DIRECTION_REVERSE)
pid_ref *= -1;
// 获取最终输出
set = (int16_t)pid_ref;
// 分组填入发送数据
group = motor->sender_group;
num = motor->message_num;
sender_assignment[group].tx_buff[2 * num] = (uint8_t)(set >> 8); // 低八位
sender_assignment[group].tx_buff[2 * num + 1] = (uint8_t)(set & 0x00ff); // 高八位
// 若该电机处于停止状态,直接将buff置零
if (motor->stop_flag == MOTOR_STOP)
memset(sender_assignment[group].tx_buff + 2 * num, 0, sizeof(uint16_t));
}
// 遍历flag,检查是否要发送这一帧报文
#ifdef FDCAN
for (size_t i = 0; i < 9; ++i)
#else
for (size_t i = 0; i < 6; ++i)
#endif
{
if (sender_enable_flag[i])
{
CANTransmit(&sender_assignment[i], 1);
}
}
}

View File

@@ -0,0 +1,120 @@
//
// Created by nie_b on 2026/2/23.
//
#ifndef TRONONEH7_SCAFFOLD_DJI_MOTOR_H
#define TRONONEH7_SCAFFOLD_DJI_MOTOR_H
#include "bsp_fdcan.h"
#include "pid.h"
#include "motor_def.h"
#include "stdint.h"
#include "daemon.h"
#define DJI_MOTOR_CNT 12
/* 滤波系数设置为1的时候即关闭滤波 */
#define SPEED_SMOOTH_COEF 0.85f // 最好大于0.85
#define CURRENT_SMOOTH_COEF 0.9f // 必须大于0.9
#define ECD_ANGLE_COEF_DJI 0.043945f // (360/8192),将编码器值转化为角度制
/* DJI电机CAN反馈信息*/
typedef struct
{
uint16_t last_ecd; // 上一次读取的编码器值
uint16_t ecd; // 0-8191,刻度总共有8192格
float angle_single_round; // 单圈角度
float speed_aps; // 角速度,单位为:度/秒
int16_t real_current; // 实际电流
uint8_t temperature; // 温度 Celsius
float total_angle; // 总角度,注意方向
int32_t total_round; // 总圈数,注意方向
} DJI_Motor_Measure_s;
/**
* @brief DJI intelligent motor typedef
*
*/
typedef struct
{
DJI_Motor_Measure_s measure; // 电机测量值
Motor_Control_Setting_s motor_settings; // 电机设置
Motor_Controller_s motor_controller; // 电机控制器
FDCANInstance *motor_fdcan_instance; // 电机CAN实例
// 分组发送设置
uint8_t sender_group;
uint8_t message_num;
Motor_Type_e motor_type; // 电机类型
Motor_Working_Type_e stop_flag; // 启停标志
Daemon_Instance* daemon;
uint32_t feed_cnt;
float dt;
} DJIMotorInstance;
/**
* @brief 调用此函数注册一个DJI智能电机,需要传递较多的初始化参数,请在application初始化的时候调用此函数
* 推荐传参时像标准库一样构造initStructure然后传入此函数.
* recommend: type xxxinitStructure = {.member1=xx,
* .member2=xx,
* ....};
* 请注意不要在一条总线上挂载过多的电机(超过6个),若一定要这么做,请降低每个电机的反馈频率(设为500Hz),
* 并减小DJIMotorControl()任务的运行频率.
*
* @attention M3508和M2006的反馈报文都是0x200+id,而GM6020的反馈是0x204+id,请注意前两者和后者的id不要冲突.
* 如果产生冲突,在初始化电机的时候会进入IDcrash_Handler(),可以通过debug来判断是否出现冲突.
*
* @param config 电机初始化结构体,包含了电机控制设置,电机PID参数设置,电机类型以及电机挂载的CAN设置
*
* @return DJIMotorInstance*
*/
DJIMotorInstance *DJIMotorInit(Motor_Init_Config_s *config);
/**
* @brief 被application层的应用调用,给电机设定参考值.
* 对于应用,可以将电机视为传递函数为1的设备,不需要关心底层的闭环
*
* @param motor 要设置的电机
* @param ref 设定参考值
*/
void DJIMotorSetRef(DJIMotorInstance *motor, float ref);
/**
* @brief 切换反馈的目标来源,如将角速度和角度的来源换为IMU(小陀螺模式常用)
*
* @param motor 要切换反馈数据来源的电机
* @param loop 要切换反馈数据来源的控制闭环
* @param type 目标反馈模式
*/
void DJIMotorChangeFeed(DJIMotorInstance *motor, Closeloop_Type_e loop, Feedback_Source_e type);
/**
* @brief 该函数被motor_task调用运行在rtos上,motor_stask内通过osDelay()确定控制频率
*/
void DJIMotorControl();
/**
* @brief 停止电机,注意不是将设定值设为零,而是直接给电机发送的电流值置零
*
*/
void DJIMotorStop(DJIMotorInstance *motor);
/**
* @brief 启动电机,此时电机会响应设定值
* 初始化时不需要此函数,因为stop_flag的默认值为0
*
*/
void DJIMotorEnable(DJIMotorInstance *motor);
/**
* @brief 修改电机闭环目标(外层闭环)
*
* @param motor 要修改的电机实例指针
* @param outer_loop 外层闭环类型
*/
void DJIMotorOuterLoop(DJIMotorInstance *motor, Closeloop_Type_e outer_loop);
#endif // TRONONEH7_SCAFFOLD_DJI_MOTOR_H

View File

@@ -1 +1,469 @@
# 大疆电机
# 大疆电机
# dji_motor
> TODO:
>
> 1. 给不同的电机设置不同的低通滤波器惯性系数而不是统一使用宏
> 2. 为M2006和M3508增加开环的零位校准函数
---
> 建议将电机的反馈频率通过RoboMaster Assistant统一设置为500Hz。当前默认的`MotorTask()`执行频率为500Hz若不修改电机反馈频率可能导致单条总线挂载的电机数量有限且容易出现帧错误和仲裁失败的情况。
## 总览和封装说明
> 如果你不需要理解该模块的工作原理,你只需要查看这一小节。
dji_motor模块对DJI智能电机包括M2006M3508以及GM6020进行了详尽的封装。你不再需要关心PID的计算以及CAN报文的发送和接收解析你只需要专注于根据应用层的需求设定合理的期望值并通过`DJIMotorSetRef()`设置对应电机的输入参考即可。
**==设定值的单位==**
1. ==位置环为**角度制**0-360total_angle可以为任意值==
2. ==速度环为角速度,单位为**度/每秒**deg/sec==
3. ==电流环为A==
4. ==GM6020的输入设定为**力矩**,待测量(-30000~30000==
==M3508的输入设定为-20A~20A -16384~16384==
==M2006的输入设定为-10A~10A -10000~10000==
如果你希望更改电机的反馈来源,比如进入小陀螺模式/视觉模式这时候你想要云台保持静止使用IMU的yaw角度值作为反馈来源只需要调用`DJIMotorChangeFeed()`电机便可立刻切换反馈数据来源至IMU。
要获得一个电机,请通过`DJIMotorInit()`并传入一些参数,他就会返回一个电机的指针。你也不再需要查看这些电机和电调的说明书,**只需要设置其电机id**6020为拨码开关值2006和3508为电调的闪动次数该模块会自动为你计算CAN发送和接收ID并搞定所有硬件层的琐事。
初始化电机时,你需要传入的参数包括:
- **电机挂载的CAN总线设置**CAN1 or CAN2以及电机的id使用`can_instance_config_s`封装,只需要设置这两个参数:
```c
CAN_HandleTypeDef *can_handle;
uint32_t tx_id; // tx_id设置为电机id,不需要查说明书计算直接为电调的闪动次数或拨码开关值为1-8
```
- **电机类型**,使用`Motor_Type_e`
```c
GM6020 = 0
M3508 = 1
M2006 = 2
```
- **电机控制设置**
- 闭环类型
```c
OPEN_LOOP
CURRENT_LOOP
SPEED_LOOP
ANGLE_LOOP
CURRENT_LOOP | SPEED_LOOP // 同时对电流和速度闭环
SPEED_LOOP | ANGLE_LOOP // 同时对速度和位置闭环
CURRENT_LOOP | SPEED_LOOP |ANGLE_LOOP // 三环全开
```
- 是否反转
```c
MOTOR_DIRECTION_NORMAL
MOTOR_DIRECTION_REVERSE
```
- 是否其他反馈来源,以及他们对应的数据指针(如果有的话)
```c
MOTOR_FEED = 0
OTHER_FEED = 1
---
// 电流只能从电机传感器获得所以无法设置其他来源
```
- 每个环的PID参数以及是否使用改进功能以及其他反馈来源指针如果在上一步启用了其他数据来源
```c
typedef struct // config parameter
{
float Kp;
float Ki;
float Kd;
float MaxOut; // 输出限幅
// 以下是优化参数
float IntegralLimit; // 积分限幅
float DeadBand; // 死区
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;
PID_Improvement_e Improve; // 优化环节,定义在下一个代码块
} PIDInit_config_s;
// 只有当你设启用了对应的优化环节,优化参数才会生效
```
```c
typedef enum
{
NONE = 0b00000000,
Integral_Limit = 0b00000001,
Derivative_On_Measurement = 0b00000010,
Trapezoid_Intergral = 0b00000100,
Proportional_On_Measurement = 0b00001000,
OutputFilter = 0b00010000,
ChangingIntegrationRate = 0b00100000,
DerivativeFilter = 0b01000000,
ErrorHandle = 0b10000000,
} PID_Improvement_e;
// 若希望使用多个环节的优化这样就行Integral_Limit |Trapezoid_Intergral|...|...
```
```c
float *other_angle_feedback_ptr
float *other_speed_feedback_ptr
```
---
推荐的初始化参数编写格式如下:
```c
Motor_Init_Config_s config = {
.motor_type = M3508, // 要注册的电机为3508电机
.can_init_config = {.can_handle = &hcan1, // 挂载在CAN1
.tx_id = 1}, // C620每隔一段时间闪动1次,设置为1
// 采用电机编码器角度与速度反馈,启用速度环和电流环,不反转,最外层闭环为速度环
.controller_setting_init_config = {.angle_feedback_source = MOTOR_FEED,
.outer_loop_type = SPEED_LOOP,
.close_loop_type = SPEED_LOOP | CURRENT_LOOP,
.speed_feedback_source = MOTOR_FEED,
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL},
// 电流环和速度环PID参数的设置,不采用计算优化则不需要传入Improve参数
// 不使用其他数据来源(如IMU),不需要传入反馈数据变量指针
.controller_param_init_config = {.current_PID = {.Improve = 0,
.Kp = 1,
.Ki = 0,
.Kd = 0,
.DeadBand = 0,
.MaxOut = 4000},
.speed_PID = {.Improve = 0,
.Kp = 1,
.Ki = 0,
.Kd = 0,
.DeadBand = 0,
.MaxOut = 4000}}};
dji_motor_instance *djimotor = DJIMotorInit(config); // 设置好参数后进行初始化并保留返回的指针
```
---
要控制一个DJI电机我们提供了2个接口
```c
void DJIMotorSetRef(dji_motor_instance *motor, float ref);
void DJIMotorChangeFeed(dji_motor_instance *motor,
Closeloop_Type_e loop,
Feedback_Source_e type);
```
调用第一个并传入设定值它会自动根据你设定的PID参数进行动作。 如果对不同闭环都有参考输入,则设置最外层的闭环(通过此函数)并将剩下的参考输入通过前馈数据指针进行设定
调用第二个并设定要修改的反馈环节和反馈类型,它会将反馈数据指针切换到你设定好的变量(需要在初始化的时候设置反馈指针)。
**如果需要获取电机的反馈数据**(如小陀螺模式需要根据麦克纳姆轮逆运动学解算底盘速度),直接通过你拥有的`dji_motor_instance`访问成员变量:
```c
// LeftForwardMotor是一个dji_motor_instance实例
float speed=LeftForwardMotor->motor_measure->speed_rpm;
...
```
***现在忘记PID的计算和发送、接收以及协议解析专注于模块之间的逻辑交互吧。***
---
## 代码结构
.h文件内包括了外部接口和类型定义,以及模块对应的宏。c文件内为私有函数和外部接口的定义。
motor_def.h内包含了一些电机通用的定义。
## 类型定义
```c
#define DJI_MOTOR_CNT 12
#define SPEED_SMOOTH_COEF 0.9f // better to be greater than 0.85
#define CURRENT_SMOOTH_COEF 0.98f // this coef must be greater than 0.95
typedef struct /* DJI电机CAN反馈信息*/
{
uint16_t ecd;
uint16_t last_ecd;
int16_t speed_rpm;
int16_t given_current;
uint8_t temperate;
int16_t total_round;
int32_t total_angle;
} dji_motor_measure;
typedef struct
{
/* motor measurement recv from CAN feedback */
dji_motor_measure motor_measure;
/* basic config of a motor*/
Motor_Control_Setting_s motor_settings;
/* controller used in the motor (3 loops)*/
Motor_Controller_s motor_controller;
/* the CAN instance own by motor instance*/
can_instance motor_can_instance;
/* sender assigment*/
uint8_t sender_group;
uint8_t message_num;
uint8_t stop_flag;
Motor_Type_e motor_type;
} dji_motor_instance;
```
- `DJI_MOTOR_CNT`是允许的最大DJI电机数量根据经验暂定为每个CAN6个防止出现拥塞。
- `SPEED_SMOOTH_COEF`和`CURRENT_SMOOTH_COEF`是电机反馈的电流和速度数据低通滤波器惯性系数,数值越小平滑效果越大,但滞后也越大。设定时不应当低于推荐值。
- `dji_motor_measure`是DJI电机的反馈信息包括当前编码器值、上次测量编码器值、速度、电流、温度、总圈数和单圈角度。
- `Motor_Control_Setting_s`的定义在`motor_def.h`之中,它和`Motor_Controller_s`都是所有电机通用的组件如M3508LK9025HT04MT6023等其包含内容如下
```c
typedef struct /* 电机控制配置 */
{
Closeloop_Type_e outer_loop_type;
Closeloop_Type_e close_loop_type;
Motor_Reverse_Flag_e motor_reverse_flag;
Feedback_Source_e angle_feedback_source;
Feedback_Source_e speed_feedback_source;
} Motor_Control_Setting_s;
```
`Motor_Control_Setting_s`里包含了电机的闭环类型,反转标志以及额外的反馈来源标志。
- 闭环类型指示该电机使用的控制器配置,其枚举定义如下:
```c
typedef enum
{
CURRENT_LOOP = 0b0001,
SPEED_LOOP = 0b0010,
ANGLE_LOOP = 0b0100,
_ = 0b0011,
__ = 0b0110,
___ = 0b0111
} Closeloop_Type_e;
```
以M3508为例假设需要进行**速度闭环**和**电流闭环**,那么在初始化时就将这个变量的值设为`CURRENT_LOOP | SPEED_LOOP`。在`DJIMotorControl()`中,函数将会根据此标志位判断设定的参考值需要经过那些控制器的计算。
另外,你还需要设置当前电机的最外层闭环,即电机的闭环目标为什么类型的值。初始化时需要设置`outer_loop_type`。以M2006作为拨盘电机时为例你希望它在单发/双发等固定发射数量的模式下对位置进行闭环(拨盘转过一定角度对应拨出一颗弹丸),但你也有可能希望在连发的时候让拨盘连续的转动,以一定的频率发射弹丸。我们提供了`DJIMotorOuterLoop()`用于修改电机的外层闭环,改变电机的闭环对象。
> 注意务必分清串级控制多环和外层闭环的区别。前者是为了提高内环的性能使得其能更好地跟随外环参考值而后者描述的是系统真实的控制目标闭环目标。如3508没有电流环仍然可以对速度完成闭环对于高层的应用来说它们本质上不关心电机内部是否还有电流环它们只把外层闭环为速度的电机当作一个**速度伺服执行器****外层闭环**描述的就是真正的闭环目标。
- 为了避开恼人的正负号,提高代码的可维护性,在初始化电机时设定`motor_reverse_flag`使得所有电机都按照你想要的方向旋转,其定义如下:
```c
typedef enum
{
MOTOR_DIRECTION_NORMAL = 0,
MOTOR_DIRECTION_REVERSE = 1
} Motor_Reverse_Flag_e;
```
- `speed_feedback_source`以及`angle_feedback_source`是指示电机反馈来源的标志位。一般情况下电机使用自身的编码器作为控制反馈量。但在某些时候如小陀螺模式云台电机会使用IMU的姿态数据作为反馈数据来源。其定义如下
```c
typedef enum
{
MOTOR_FEED = 0,
OTHER_FEED = 1
} Feedback_Source_e;
```
**注意,如果启用其他数据来源,你需要在电机的控制器配置`Motor_Controller_s`下的`other_xxx_feedback_ptr`中指定其他数据来源。**
你可以在`DJIMotorChangeFeed()`中修改电机的数据来源。
- `Motor_Controller_s`的定义也在`motor_def.h`之中:
```c
/* 电机控制器,包括其他来源的反馈数据指针,3环控制器和电机的参考输入*/
typedef struct
{
float *other_angle_feedback_ptr;
float *other_speed_feedback_ptr;
PID_t current_PID;
PID_t speed_PID;
PID_t angle_PID;
float pid_ref; // 将会作为每个环的输入和输出顺次通过串级闭环
} Motor_Controller_s;
```
两个`float*`指针应当指向其他反馈来源数据(如果有的话,需要在`motor_settings`中设定)。
三个PID分别为三个控制闭环所用在`DJIMotorControl()`中,该函数会根据`close_loop_type`的设定计算对应的闭环。
**`pid_ref`是控制的设定值app层的应用想要更改电机的输出就要调用`DJIMotorSetRef()`更改此值。**
- `dji_motor_instance`是一个DJI电机实例。一个电机实例内包含电机的反馈信息电机的控制设置电机控制器电机对应的CAN实例以及电机的类型由于DJI电机支持**一帧报文控制至多4个电机**,该结构体还包含了用于给电机分组发送进行特殊处理的`sender_group`和`message_num`(具体实现细节参考`MotorSenderGrouping()`函数)。
## 外部接口
```c
dji_motor_instance *DJIMotorInit(can_instance_config config,
Motor_Control_Setting_s motor_setting,
Motor_Controller_Init_s controller_init,
Motor_Type_e type);
void DJIMotorSetRef(dji_motor_instance *motor, float ref);
void DJIMotorChangeFeed(dji_motor_instance *motor,
Closeloop_Type_e loop,
Feedback_Source_e type);
void DJIMotorControl();
void DJIMotorStop(dji_motor_instance *motor);
void DJIMotorEnable(dji_motor_instance *motor);
void DJIMotorOuterLoop(dji_motor_instance *motor);
```
- `DJIMotorInit()`是用于初始化电机对象的接口传入包括电机can配置、电机控制配置、电机控制器配置以及电机类型在内的初始化参数。**它将会返回一个电机实例指针**,你应当在应用层保存这个指针,这样才能操控这个电机。
- `DJIMotorSetRef()`是设定电机输出的接口,**在调用这个函数的时候,你可以认为你的设定值会直接转变为电机的输出**。`DJIMotorControl()`会帮你完成闭环计算不用担心PID。
- `DJIMotorChangeFeed()`一般在更改云台或底盘的运动模式的时候被调用传入要修改反馈来源的电机实例指针、要修改的闭环以及反馈来源类型。如希望切换到IMU的yaw值作为云台设定值传入yaw轴电机实例和`ANGLE_LOOP`(位置环)、`OTHER_FEED`(启用其他数据来源)即可。当然,你需要在初始化的时候设定`motor_controller`中的 `other_angle_feedback_ptr`使其指向yaw值的变量。
- `DJIMotorControl()`是根据电机的配置计算控制值的函数。该函数在`motor_task.c`中被调用应当在freeRTOS中以一定频率运行。此函数为PID的计算进行了彻底的封装要修改电机的参考输入请在app层的应用中调用`DJIMotorSetRef()`。
该函数的具体实现请参照代码,注释已经较为清晰。流程大致为:
1. 根据电机的初始化控制配置,计算各个控制闭环
2. 根据反转标志位,确定是否将输出反转
3. 根据每个电机的发送分组将最终输出值填入对应的分组buff
4. 检查每一个分组,若该分组有电机,发送报文
- `DJIMotorStop()`和`DJIMotorEnable()`用于控制电机的启动和停止。当电机被设为stop的时候不会响应任何的参考输入。
- `DJIMotorOuterLoop()`用于修改电机的外部闭环类型,即电机的真实闭环目标。
## 私有函数和变量
在.c文件内设为static的函数和变量
```c
static uint8_t idx = 0; // register idx,是该文件的全局电机索引,在注册时使用
static dji_motor_instance *dji_motor_info[DJI_MOTOR_CNT] = {NULL};
```
这是管理所有电机实例的入口。idx用于电机初始化。
```c
#define PI2 (3.141592f * 2)
#define ECD_ANGLE_COEF_DJI 3.835e-4 // ecd/8192*pi
```
这两个宏用于在电机反馈信息中的多圈角度计算将编码器的0~8192转化为角度表示。
```c
/* @brief 由于DJI电机发送以四个一组的形式进行,故对其进行特殊处理,用6个(2can*3group)can_instance专门负责发送
* 该变量将在 DJIMotorControl() 中使用,分组在 MotorSenderGrouping()中进行
*
* can1: [0]:0x1FF,[1]:0x200,[2]:0x2FF
* can2: [0]:0x1FF,[1]:0x200,[2]:0x2FF */
static can_instance sender_assignment[6] =
{
[0] = {.can_handle = &hcan1, .txconf.StdId = 0x1ff, .txconf.IDE = CAN_ID_STD, .txconf.RTR = CAN_RTR_DATA, .txconf.DLC = 0x08, .tx_buff = {0}},
...
...
};
static uint8_t sender_enable_flag[6] = {0};
```
- 这些是电机分组发送所需的变量。注册电机时会根据挂载的总线以及发送id将电机分组。在CAN发送电机控制信息的时候根据`sender_assignment[]`保存的分组进行发送,而不会使用电机实例自带的`can_instance`。
- DJI电机共有3种分组分别为0x1FF,0x200,0x2FF。注册电机的时候`MotorSenderGrouping()`函数会根据发送id计算出CAN的`tx_id`(即上述三个中的一个)和`rx_id`。然后为电机实例分配用于指示其在`sender_assignment[]`中的编号的 `sender_group`和其在该发送组中的位置`message_num`(一帧报文可以发送四条控制指令,`message_num`会指定电机是这四个中的哪一个)。具体的分配请查看`MotorSenderGrouping()`的定义。
- 当某一个分组有电机注册时,该分组的索引将会在`sender_enable_flag`[]中被置1这样就可以避免发送没有电机注册的报文防止总线拥塞。具体的在`DecodeDJIMotor()`中,该函数会查看`sender_enable_flag[]`的每一个位置,确定这一组是否有电机被注册,若有则发送`sender_assignment[]`中对应位置的`tx_buff`。
```c
static void IDcrash_Handler(uint8_t conflict_motor_idx, uint8_t temp_motor_idx)
static void MotorSenderGrouping(can_instance_config *config)
static void DecodeDJIMotor(can_instance *_instance)
```
- `IDcrash_Handler()`在电机id发生冲突的时候会被`MotorSenderGrouping()`调用陷入死循环之中并把冲突的id保存在函数里。这样就可以通过debug确定是否发生冲突以及冲突的编号。
- `MotorSenderGrouping()`被`DJIMotorInit()`调用他将会根据电机id计算出CAN的发送和接收ID并根据发送ID对电机进行分组。
- `DecodeDJIMotor()`是解析电机反馈报文的函数,在`DJIMotorInit()`中会将其注册到该电机实例对应的`can_instance`中(即`can_instance`的`can_module_callback()`)。这样,当该电机的反馈报文到达时,`bsp_can.c`中的回调函数会调用解包函数进行反馈数据解析。
该函数还会对电流和速度反馈值进行滤波,消除高频噪声;同时计算多圈角度和单圈绝对角度。
**电机反馈的电流值为说明书中的映射值,需转换为实际值。**
**反馈的速度单位是rpm转每分钟转换为角度每秒。**
**反馈的位置是编码器值0~8191转换为角度。**
## 使用范例
```c
//初始化设置
Motor_Init_Config_s config = {
.motor_type = GM6020,
.can_init_config = {
.can_handle = &hcan1,
.tx_id = 6
},
.controller_setting_init_config = {
.angle_feedback_source = MOTOR_FEED,
.outer_loop_type = SPEED_LOOP,
.close_loop_type = SPEED_LOOP | ANGLE_LOOP,
.speed_feedback_source = MOTOR_FEED,
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL
},
.controller_param_init_config = {
.angle_PID = {
.Improve = 0,
.Kp = 1,
.Ki = 0,
.Kd = 0,
.DeadBand = 0,
.MaxOut = 4000},
.speed_PID = {
.Improve = 0,
.Kp = 1,
.Ki = 0,
.Kd = 0,
.DeadBand = 0,
.MaxOut = 4000
}
}
};
//注册电机并保存实例指针
dji_motor_instance *djimotor = DJIMotorInit(&config);
```
然后在任务中修改电机设定值即可实现控制:
```
DJIMotorSetRef(djimotor, 10);
```
前提是已经将`DJIMotorControl()`放入实时系统任务当中或以一定d。你也可以单独执行`DJIMotorControl()`。

View File

@@ -0,0 +1,274 @@
#include "dm_motor.h"
#include "bsp_log.h"
#include "cmsis_os.h"
#include "daemon.h"
#include "general_def.h"
#include "memory.h"
#include "motor_def.h"
#include "stdlib.h"
#include "string.h"
#include "user_lib.h"
static uint8_t idx;
static DMMotorInstance *dm_motor_instance[DM_MOTOR_CNT];
static TaskHandle_t dm_task_handle[DM_MOTOR_CNT];
/* 两个用于将uint值和float值进行映射的函数,在设定发送值和解析反馈值时使用 */
static uint16_t float_to_uint(float x, float x_min, float x_max, uint8_t bits)
{
float span = x_max - x_min;
float offset = x_min;
return (uint16_t)((x - offset) * ((float)((1 << bits) - 1)) / span);
}
static float uint_to_float(int x_int, float x_min, float x_max, int bits)
{
float span = x_max - x_min;
float offset = x_min;
return ((float)x_int) * span / ((float)((1 << bits) - 1)) + offset;
}
static void DMMotorSetMode(DMMotor_Mode_e cmd, DMMotorInstance *motor)
{
memset(motor->motor_can_instance->tx_buff, 0xff, 7); // 发送电机指令的时候前面7bytes都是0xff
motor->motor_can_instance->tx_buff[7] = (uint8_t)cmd; // 最后一位是命令id
CANTransmit(motor->motor_can_instance, 1);
}
static void DMMotorDecode(FDCANInstance *motor_can)
{
uint16_t tmp; // 用于暂存解析值,稍后转换成float数据,避免多次创建临时变量
uint8_t *rxbuff = motor_can->rx_buff;
DMMotorInstance *motor = (DMMotorInstance *)motor_can->id;
DM_Motor_Measure_s *measure = &(motor->measure); // 将can实例中保存的id转换成电机实例的指针
DaemonReload(motor->motor_daemon);
measure->last_position = measure->position;
// 区分一控四模式和MIT模式的反馈解析
if (motor->ctrl_mode == DM_CTRL_ONE_TO_FOUR)
{
// 标识符为0x300+电机ID时的解析逻辑
// D[0], D[1] 为位置高/低8位范围0-8191对应一圈位置
tmp = (uint16_t)((rxbuff[0] << 8) | rxbuff[1]);
measure->position = (float)tmp;
// D[2], D[3] 为速度高/低8位单位rpm放大一百倍
int16_t vel_tmp = (int16_t)((rxbuff[2] << 8) | rxbuff[3]);
measure->velocity = (float)vel_tmp / 100.0f;
// D[4], D[5] 为扭矩电流高/低8位单位mA
int16_t torq_tmp = (int16_t)((rxbuff[4] << 8) | rxbuff[5]);
measure->torque = (float)torq_tmp / 1000.0f;
// D[6] 为电机线圈温度
measure->T_Mos = (float)rxbuff[6];
// D[7] 为错误状态
measure->state = rxbuff[7];
}
else
{
// 原MIT反馈解析逻辑
tmp = (uint16_t)((rxbuff[1] << 8) | rxbuff[2]);
measure->position = uint_to_float(tmp, DM_P_MIN, DM_P_MAX, 16);
tmp = (uint16_t)((rxbuff[3] << 4) | rxbuff[4] >> 4);
measure->velocity = uint_to_float(tmp, DM_V_MIN, DM_V_MAX, 12);
tmp = (uint16_t)(((rxbuff[4] & 0x0f) << 8) | rxbuff[5]);
measure->torque = uint_to_float(tmp, DM_T_MIN, DM_T_MAX, 12);
measure->T_Mos = (float)rxbuff[6];
measure->T_Rotor = (float)rxbuff[7];
}
}
static void DMMotorLostCallback(void *motor_ptr)
{
}
void DMMotorCaliEncoder(DMMotorInstance *motor)
{
DMMotorSetMode(DM_CMD_ZERO_POSITION, motor);
DWT_Delay(1);
}
DMMotorInstance *DMMotorInit(Motor_Init_Config_s *config)
{
DMMotorInstance *motor = (DMMotorInstance *)malloc(sizeof(DMMotorInstance));
memset(motor, 0, sizeof(DMMotorInstance));
// 默认初始化为MIT模式
motor->ctrl_mode = DM_CTRL_MIT;
motor->motor_settings = config->controller_setting_init_config;
PIDInit(&motor->current_PID, &config->controller_param_init_config.current_PID);
PIDInit(&motor->speed_PID, &config->controller_param_init_config.speed_PID);
PIDInit(&motor->angle_PID, &config->controller_param_init_config.angle_PID);
motor->other_angle_feedback_ptr = config->controller_param_init_config.other_angle_feedback_ptr;
motor->other_speed_feedback_ptr = config->controller_param_init_config.other_speed_feedback_ptr;
config->fdcan_init_config.can_module_callback = DMMotorDecode;
config->fdcan_init_config.id = motor;
motor->motor_can_instance = CANRegister(&config->fdcan_init_config);
Daemon_Init_Config_s conf = {
.callback = DMMotorLostCallback,
.owner_id = motor,
.reload_count = 10,
};
motor->motor_daemon = DaemonRegister(&conf);
DMMotorEnable(motor);
DMMotorSetMode(DM_CMD_MOTOR_MODE, motor);
DWT_Delay(1);
DMMotorCaliEncoder(motor);
DWT_Delay(1);
dm_motor_instance[idx++] = motor;
return motor;
}
void DMMotorSetRef(DMMotorInstance *motor, float ref)
{
motor->pid_ref = ref;
}
void DMMotorEnable(DMMotorInstance *motor)
{
motor->stop_flag = MOTOR_ENALBED;
}
void DMMotorStop(DMMotorInstance *motor)//不使用使能模式是因为需要收到反馈
{
motor->stop_flag = MOTOR_STOP;
}
void DMMotorOuterLoop(DMMotorInstance *motor, Closeloop_Type_e type)
{
motor->motor_settings.outer_loop_type = type;
}
void DMMotorSetCtrlMode(DMMotorInstance *motor, DMMotor_Ctrl_Mode_e mode)
{
motor->ctrl_mode = mode;
}
// 一控四下发指令支持1帧控制4个电机由外部统一调用不要在DMMotorTask中高频调用此函数
void DMMotorSendOneToFourGroup(FDCANInstance *can_instance, uint8_t group, float i1, float i2, float i3, float i4)
{
// 根据电机ID组配置对应的报文ID[1,4]为0x3FE, [5,8]为0x4FE
uint32_t tx_id = (group == 1) ? 0x3FE : 0x4FE;
// 控制电流为标幺值采用力位混控i_des相同的16位映射机制进行量化
uint16_t cur1 = float_to_uint(i1, DM_T_MIN, DM_T_MAX, 16);
uint16_t cur2 = float_to_uint(i2, DM_T_MIN, DM_T_MAX, 16);
uint16_t cur3 = float_to_uint(i3, DM_T_MIN, DM_T_MAX, 16);
uint16_t cur4 = float_to_uint(i4, DM_T_MIN, DM_T_MAX, 16);
uint32_t old_id = can_instance->tx_id;
can_instance->tx_id = tx_id;
// 数据段填充先低8位再高8位
can_instance->tx_buff[0] = (uint8_t)(cur1 & 0xFF);
can_instance->tx_buff[1] = (uint8_t)(cur1 >> 8);
can_instance->tx_buff[2] = (uint8_t)(cur2 & 0xFF);
can_instance->tx_buff[3] = (uint8_t)(cur2 >> 8);
can_instance->tx_buff[4] = (uint8_t)(cur3 & 0xFF);
can_instance->tx_buff[5] = (uint8_t)(cur3 >> 8);
can_instance->tx_buff[6] = (uint8_t)(cur4 & 0xFF);
can_instance->tx_buff[7] = (uint8_t)(cur4 >> 8);
CANTransmit(can_instance, 1);
can_instance->tx_id = old_id; // 恢复旧有配置
}
// 一控四模式下特殊清零指令
void DMMotorSetZeroOneToFour(FDCANInstance *can_instance, uint16_t target_can_id)
{
uint32_t old_id = can_instance->tx_id;
can_instance->tx_id = 0x7FF; // 零点设置特殊指令报文ID
// 依序填入CANID和固定魔法字
can_instance->tx_buff[0] = (uint8_t)(target_can_id & 0xFF);
can_instance->tx_buff[1] = (uint8_t)(target_can_id >> 8);
can_instance->tx_buff[2] = 0x55;
can_instance->tx_buff[3] = 0x50;
can_instance->tx_buff[4] = 0x00;
can_instance->tx_buff[5] = 0x00;
can_instance->tx_buff[6] = 0x00;
can_instance->tx_buff[7] = 0x00;
CANTransmit(can_instance, 1);
can_instance->tx_id = old_id;
}
//@Todo: MIT模式目前只实现了力控更多位控PID等请自行添加
void DMMotorTask(void *argument)
{
float pid_ref, set;
DMMotorInstance *motor = (DMMotorInstance *)argument;
Motor_Control_Setting_s *setting = &motor->motor_settings;
DMMotor_Send_s motor_send_mailbox;
while (1)
{
// 若当前实例设为了一控四模式不应由单独的电机Task发送报文
// 需要在用户外部的任务里定期调用 DMMotorSendOneToFourGroup()
if (motor->ctrl_mode == DM_CTRL_ONE_TO_FOUR)
{
osDelay(2);
continue;
}
pid_ref = motor->pid_ref;
set = pid_ref;
if (setting->motor_reverse_flag == MOTOR_DIRECTION_REVERSE)
set *= -1;
LIMIT_MIN_MAX(set, DM_T_MIN, DM_T_MAX);
motor_send_mailbox.position_des = float_to_uint(0, DM_P_MIN, DM_P_MAX, 16);
motor_send_mailbox.velocity_des = float_to_uint(0, DM_V_MIN, DM_V_MAX, 12);
motor_send_mailbox.torque_des = float_to_uint(pid_ref, DM_T_MIN, DM_T_MAX, 12);
motor_send_mailbox.Kp = 0;
motor_send_mailbox.Kd = 0;
if(motor->stop_flag == MOTOR_STOP)
motor_send_mailbox.torque_des = float_to_uint(0, DM_T_MIN, DM_T_MAX, 12);
motor->motor_can_instance->tx_buff[0] = (uint8_t)(motor_send_mailbox.position_des >> 8);
motor->motor_can_instance->tx_buff[1] = (uint8_t)(motor_send_mailbox.position_des);
motor->motor_can_instance->tx_buff[2] = (uint8_t)(motor_send_mailbox.velocity_des >> 4);
motor->motor_can_instance->tx_buff[3] = (uint8_t)(((motor_send_mailbox.velocity_des & 0xF) << 4) | (motor_send_mailbox.Kp >> 8));
motor->motor_can_instance->tx_buff[4] = (uint8_t)(motor_send_mailbox.Kp);
motor->motor_can_instance->tx_buff[5] = (uint8_t)(motor_send_mailbox.Kd >> 4);
motor->motor_can_instance->tx_buff[6] = (uint8_t)(((motor_send_mailbox.Kd & 0xF) << 4) | (motor_send_mailbox.torque_des >> 8));
motor->motor_can_instance->tx_buff[7] = (uint8_t)(motor_send_mailbox.torque_des);
CANTransmit(motor->motor_can_instance, 1);
osDelay(2);
}
}
void DMMotorControlInit()
{
// 遍历所有电机实例,创建任务
if (!idx)
return;
// 注意CMSIS-RTOS V2的osThreadDef不支持动态生成的名称
// 我们需要为每个电机创建独立的线程定义或使用不同的方法
// 方案1使用循环和预定义的线程定义如果电机数量固定
// 这里改为直接使用FreeRTOS原生API创建线程更加灵活可靠
for (size_t i = 0; i < idx; i++)
{
// 使用FreeRTOS原生API创建线程
// 参数:线程函数、线程名称、堆栈大小、参数、优先级、线程句柄
if (xTaskCreate(DMMotorTask, "DMMotorTask", 128, dm_motor_instance[i], osPriorityNormal, &dm_task_handle[i]) != pdPASS)
{
LOGERROR("[DM_Motor] Failed to create motor thread for motor %d", i);
}
}
}

View File

@@ -0,0 +1,96 @@
#ifndef DM_MOTOR_H
#define DM_MOTOR_H
#include <stdint.h>
#include "bsp_fdcan.h"
#include "pid.h"
#include "motor_def.h"
#include "daemon.h"
#define DM_MOTOR_CNT 4
#define DM_P_MIN (-12.5f)
#define DM_P_MAX 12.5f
#define DM_V_MIN (-45.0f)
#define DM_V_MAX 45.0f
#define DM_T_MIN (-18.0f)
#define DM_T_MAX 18.0f
// 新增:电机控制模式枚举
typedef enum {
DM_CTRL_MIT = 0,//MIT模式默认的单电机控制模式
DM_CTRL_ONE_TO_FOUR = 1, // 一控四模式
} DMMotor_Ctrl_Mode_e;
typedef struct
{
uint8_t id;
uint8_t state;
float velocity;
float last_position;
float position;
float torque;
float T_Mos;
float T_Rotor;
int32_t total_round;
}DM_Motor_Measure_s;
typedef struct
{
uint16_t position_des;
uint16_t velocity_des;
uint16_t torque_des;
uint16_t Kp;
uint16_t Kd;
}DMMotor_Send_s;
typedef struct
{
DM_Motor_Measure_s measure;
Motor_Control_Setting_s motor_settings;
PIDInstance current_PID;
PIDInstance speed_PID;
PIDInstance angle_PID;
float *other_angle_feedback_ptr;
float *other_speed_feedback_ptr;
float *speed_feedforward_ptr;
float *current_feedforward_ptr;
float pid_ref;
Motor_Working_Type_e stop_flag;
FDCANInstance *motor_can_instance;
Daemon_Instance* motor_daemon;
uint32_t lost_cnt;
// 新增:当前电机的控制模式标志
DMMotor_Ctrl_Mode_e ctrl_mode;
}DMMotorInstance;
typedef enum
{
DM_CMD_MOTOR_MODE = 0xfc, // 使能,会响应指令
DM_CMD_RESET_MODE = 0xfd, // 停止
DM_CMD_ZERO_POSITION = 0xfe, // 将当前的位置设置为编码器零位
DM_CMD_CLEAR_ERROR = 0xfb // 清除电机过热错误
}DMMotor_Mode_e;
DMMotorInstance *DMMotorInit(Motor_Init_Config_s *config);
void DMMotorSetRef(DMMotorInstance *motor, float ref);
void DMMotorOuterLoop(DMMotorInstance *motor,Closeloop_Type_e closeloop_type);
void DMMotorEnable(DMMotorInstance *motor);
void DMMotorStop(DMMotorInstance *motor);
void DMMotorCaliEncoder(DMMotorInstance *motor);
void DMMotorControlInit();
// 新增:设置电机控制模式
void DMMotorSetCtrlMode(DMMotorInstance *motor, DMMotor_Ctrl_Mode_e mode);
// 新增一控四模式下单控制帧发送4个电机的电流
void DMMotorSendOneToFourGroup(FDCANInstance *can_instance, uint8_t group, float i1, float i2, float i3, float i4);
// 新增:一控四模式下的零点设置
void DMMotorSetZeroOneToFour(FDCANInstance *can_instance, uint16_t target_can_id);
#endif // !DMMOTOR

View File

@@ -1 +1,94 @@
# 达妙电机
# 达妙电机
`gimbal.c` 中进行达妙电机的初始化配置并使用“一拖四”模式发送指令,你可以按照以下结构来组织代码。
### 1. 初始化配置与指定ID
在初始化阶段,你需要先配置好 `Motor_Init_Config_s`,并通过刚才新增的 `DMMotorSetCtrlMode` 函数将实例切换为一拖四模式。电机的 **ID** 是在 `can_init_config.tx_id` 中指定的。
```c
#include "DMmotor.h"
// 1. 定义电机配置结构体 (以ID为1的电机为例)
static Motor_Init_Config_s gimbal_dm_config_id1 = {
.can_init_config = {
.can_handle = &hcan1, // 指定使用的CAN外设句柄例如 hcan1 或 hcan2
.tx_id = 0x01, // 【指定电机ID】这里填入电机的实际ID如 1
},
.controller_param_init_config = {
// 虽然一控四主要是直接下发电流但为了结构完整性或外环计算可配置相关PID
.current_PID = {
.Kp = 6.0f,
.Ki = 0.0f,
.Kd = 0.495f,
.MaxOut = 45.0f,
},
},
.controller_setting_init_config = {
.motor_reverse_flag = MOTOR_DIRECTION_NORMAL,
}
};
// 2. 声明电机实例指针
DMMotorInstance *gimbal_motor_1;
// 如果同一条总线上有另外三个电机,你需要分别为它们声明实例并配置 tx_id = 2, 3, 4
void Gimbal_Init(void)
{
// 3. 调用Init函数完成底层初始化与实例分配
gimbal_motor_1 = DMMotorInit(&gimbal_dm_config_id1);
// 4. 【关键步骤】将该电机控制模式切换为一控四模式
DMMotorSetCtrlMode(gimbal_motor_1, DM_CTRL_ONE_TO_FOUR);
// (同理对ID为2、3、4的电机执行相同的Init和SetCtrlMode操作)
}
```
### 2. 使用一拖四模式发送指令
在控制任务(例如 FreeRTOS 的 `Gimbal_Task`)中,你不再需要让每个电机单独发送报文,而是通过刚才新增的 `DMMotorSendOneToFourGroup` 函数**统一打包下发**。
```c
void Gimbal_Task(void const * argument)
{
// 假设通过你的控制器如LQR或MPC等计算得出了4个电机的目标电流
// 单位与你设定DM_T_MIN、DM_T_MAX的量纲一致
float target_i1 = 1.5f;
float target_i2 = -0.5f;
float target_i3 = 2.0f;
float target_i4 = 0.0f;
while(1)
{
// ... (各种控制算法计算过程) ...
// 调用一控四发送函数打包下发控制帧
// 参数1: can_instance -> 传入挂载在该CAN总线上的任一电机实例的CAN指针即可
// 参数2: group -> 1 表示控制电机 ID[1~4] (对应报文 0x3FE)
// 2 表示控制电机 ID[5~8] (对应报文 0x4FE)
// 参数3~6: 分别对应这4个电机的电流值
DMMotorSendOneToFourGroup(gimbal_motor_1->motor_can_instace, 1,
target_i1, target_i2, target_i3, target_i4);
osDelay(2);
}
}
```
### 3. 一拖四模式下的零点设置 (附加)
如果你在调试时需要将某台电机当前的位置设置为编码器零位可以调用对应的零点校准函数传入目标电机的ID
```c
void Gimbal_Set_Zero(void)
{
// 将总线上 ID = 1 的电机当前位置设为零点
DMMotorSetZeroOneToFour(gimbal_motor_1->motor_can_instace, 0x01);
}
```
按照这种方式组织 `gimbal.c`底层的CAN发送与接收解析就会被彻底隔离开既保证了多电机联合控制的同步性又能极大节省 CAN 总线的带宽。

View File

@@ -0,0 +1,177 @@
#include "lk_motor.h"
#include "stdlib.h"
#include "general_def.h"
#include "daemon.h"
#include "bsp_dwt.h"
#include "bsp_log.h"
static uint8_t idx;
static LKMotorInstance *lkmotor_instance[LK_MOTOR_MX_CNT] = {NULL};
static FDCANInstance *sender_instance; // 多电机发送时使用的caninstance(当前保存的是注册的第一个电机的caninstance)
// 后续考虑兼容单电机和多电机指令.
/**
* @brief 电机反馈报文解析
*
* @param _instance 发生中断的caninstance
*/
static void LKMotorDecode(FDCANInstance *_instance)
{
LKMotorInstance *motor = (LKMotorInstance *)_instance->id; // 通过caninstance保存的father id获取对应的motorinstance
LKMotor_Measure_t *measure = &motor->measure;
uint8_t *rx_buff = _instance->rx_buff;
DaemonReload(motor->daemon); // 喂狗
measure->feed_dt = DWT_GetDeltaT(&measure->feed_dwt_cnt);
measure->last_ecd = measure->ecd;
measure->ecd = (uint16_t)((rx_buff[7] << 8) | rx_buff[6]);
measure->angle_single_round = ECD_ANGLE_COEF_LK * measure->ecd;
measure->speed_rads = (1 - SPEED_SMOOTH_COEF) * measure->speed_rads +
DEGREE_2_RAD * SPEED_SMOOTH_COEF * (float)((int16_t)(rx_buff[5] << 8 | rx_buff[4]));
measure->real_current = (1 - CURRENT_SMOOTH_COEF) * measure->real_current +
CURRENT_SMOOTH_COEF * (float)((int16_t)(rx_buff[3] << 8 | rx_buff[2]));
measure->temperature = rx_buff[1];
if (measure->ecd - measure->last_ecd > 65536)//MFV2是18bit编码器,这里用65536判断是否发生了跨越零点
measure->total_round--;
else if (measure->ecd - measure->last_ecd < -65536)
measure->total_round++;
measure->total_angle = measure->total_round * 360 + measure->angle_single_round;
}
static void LKMotorLostCallback(void *motor_ptr)
{
LKMotorInstance *motor = (LKMotorInstance *)motor_ptr;
LOGWARNING("[LKMotor] motor lost, id: %d", motor->motor_can_ins->tx_id);
}
LKMotorInstance *LKMotorInit(Motor_Init_Config_s *config)
{
LKMotorInstance *motor = (LKMotorInstance *)malloc(sizeof(LKMotorInstance));
motor = (LKMotorInstance *)malloc(sizeof(LKMotorInstance));
memset(motor, 0, sizeof(LKMotorInstance));
motor->motor_settings = config->controller_setting_init_config;
PIDInit(&motor->current_PID, &config->controller_param_init_config.current_PID);
PIDInit(&motor->speed_PID, &config->controller_param_init_config.speed_PID);
PIDInit(&motor->angle_PID, &config->controller_param_init_config.angle_PID);
motor->other_angle_feedback_ptr = config->controller_param_init_config.other_angle_feedback_ptr;
motor->other_speed_feedback_ptr = config->controller_param_init_config.other_speed_feedback_ptr;
config->fdcan_init_config.id = motor;
config->fdcan_init_config.can_module_callback = LKMotorDecode;
config->fdcan_init_config.rx_id = 0x140 + config->fdcan_init_config.tx_id;
config->fdcan_init_config.tx_id = config->fdcan_init_config.tx_id + 0x280 - 1; // 这样在发送写入buffer的时候更方便,因为下标从0开始,LK多电机发送id为0x280
motor->motor_can_ins = CANRegister(&config->fdcan_init_config);
if (idx == 0) // 用第一个电机的can instance发送数据
{
sender_instance = motor->motor_can_ins;
sender_instance->tx_id = 0x280; // 修改tx_id为0x280,用于多电机发送,不用管其他LKMotorInstance的tx_id,它们仅作初始化用
}
LKMotorEnable(motor);
DWT_GetDeltaT(&motor->measure.feed_dwt_cnt);
lkmotor_instance[idx++] = motor;
Daemon_Init_Config_s daemon_config = {
.callback = LKMotorLostCallback,
.owner_id = motor,
.reload_count = 5, // 50ms
};
motor->daemon = DaemonRegister(&daemon_config);
return motor;
}
/* 第一个电机的can instance用于发送数据,向其tx_buff填充数据 */
void LKMotorControl()
{
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)
{
motor = lkmotor_instance[i];
measure = &motor->measure;
setting = &motor->motor_settings;
pid_ref = motor->pid_ref;
if (setting->motor_reverse_flag == MOTOR_DIRECTION_REVERSE)
pid_ref *= -1;
// 角度环计算
if ((setting->close_loop_type & ANGLE_LOOP) && setting->outer_loop_type == ANGLE_LOOP)
{
if (setting->angle_feedback_source == OTHER_FEED)
pid_measure = *motor->other_angle_feedback_ptr;
else
pid_measure = measure->total_angle; // 修正:使用角度反馈
pid_ref = PIDCalculate(&motor->angle_PID, pid_measure, pid_ref);
if (setting->feedforward_flag & SPEED_FEEDFORWARD)
pid_ref += *motor->speed_feedforward_ptr;
}
// 速度环计算
if ((setting->close_loop_type & SPEED_LOOP) && setting->outer_loop_type & (ANGLE_LOOP | SPEED_LOOP))
{
if (setting->speed_feedback_source == OTHER_FEED) // 修正:判断speed_feedback_source
pid_measure = *motor->other_speed_feedback_ptr;
else
pid_measure = measure->speed_rads; // 修正:使用速度反馈
pid_ref = PIDCalculate(&motor->speed_PID, pid_measure, pid_ref); // 修正:使用speed_PID
if (setting->feedforward_flag & CURRENT_FEEDFORWARD)
pid_ref += *motor->current_feedforward_ptr;
}
// 电流环计算
if (setting->close_loop_type & CURRENT_LOOP)
{
pid_ref = PIDCalculate(&motor->current_PID, measure->real_current, pid_ref);
}
// 反馈方向反转
if (setting->feedback_reverse_flag == FEEDBACK_DIRECTION_REVERSE)
pid_ref *= -1;
set = (int16_t)pid_ref;
memcpy(sender_instance->tx_buff + (motor->motor_can_ins->tx_id - 0x280) * 2, &set, sizeof(uint16_t));
if (motor->stop_flag == MOTOR_STOP)
{
memset(sender_instance->tx_buff + (motor->motor_can_ins->tx_id - 0x280) * 2, 0, sizeof(uint16_t));
}
}
if (idx)
CANTransmit(sender_instance, 0.2);
}
void LKMotorStop(LKMotorInstance *motor)
{
motor->stop_flag = MOTOR_STOP;
}
void LKMotorEnable(LKMotorInstance *motor)
{
motor->stop_flag = MOTOR_ENALBED;
}
void LKMotorSetRef(LKMotorInstance *motor, float ref)
{
motor->pid_ref = ref;
}
uint8_t LKMotorIsOnline(LKMotorInstance *motor)
{
return DaemonIsOnline(motor->daemon);
}

View File

@@ -0,0 +1,98 @@
#ifndef LK_MOTOR_H
#define LK_MOTOR_H
#include "stdint.h"
#include "bsp_fdcan.h"
#include "pid.h"
#include "motor_def.h"
#include "daemon.h"
#define LK_MOTOR_MX_CNT 4 // 最多允许4个LK电机使用多电机指令,挂载在一条总线上
#define I_MIN -2000
#define I_MAX 2000
#define CURRENT_SMOOTH_COEF 0.9f
#define SPEED_SMOOTH_COEF 0.85f
#define REDUCTION_RATIO_DRIVEN 1
#define ECD_ANGLE_COEF_LK (360.0f / 65536.0f)
#define CURRENT_TORQUE_COEF_LK 0.003645f // 电流设定值转换成扭矩的系数,算出来的设定值除以这个系数就是扭矩值
typedef struct // 9025
{
uint16_t last_ecd; // 上一次读取的编码器值
uint16_t ecd; // 当前编码器值
float angle_single_round; // 单圈角度
float speed_rads; // speed rad/s
int16_t real_current; // 实际电流
uint8_t temperature; // 温度,C°
float total_angle; // 总角度
int32_t total_round; // 总圈数
float feed_dt;
uint32_t feed_dwt_cnt;
} LKMotor_Measure_t;
typedef struct
{
LKMotor_Measure_t measure;
Motor_Control_Setting_s motor_settings;
float *other_angle_feedback_ptr; // 其他反馈来源的反馈数据指针
float *other_speed_feedback_ptr;
float *speed_feedforward_ptr; // 速度前馈数据指针,可以通过此指针设置速度前馈值,或LQR等时作为速度状态变量的输入
float *current_feedforward_ptr; // 电流前馈指针
PIDInstance current_PID;
PIDInstance speed_PID;
PIDInstance angle_PID;
float pid_ref;
Motor_Working_Type_e stop_flag; // 启停标志
FDCANInstance *motor_can_ins;
Daemon_Instance *daemon;
} LKMotorInstance;
/**
* @brief 初始化LK电机
*
* @param config 电机配置
* @return LKMotorInstance* 返回实例指针
*/
LKMotorInstance *LKMotorInit(Motor_Init_Config_s *config);
/**
* @brief 设置参考值
* @attention 注意此函数设定的ref是最外层闭环的输入,若要设定内层闭环的值请通过前馈数据指针设置
*
* @param motor 要设置的电机
* @param ref 设定值
*/
void LKMotorSetRef(LKMotorInstance *motor, float ref);
/**
* @brief 为所有LK电机计算pid/反转/模式控制,并通过bspcan发送电流值(发送CAN报文)
*
*/
void LKMotorControl();
/**
* @brief 停止LK电机,之后电机不会响应任何指令
*
* @param motor
*/
void LKMotorStop(LKMotorInstance *motor);
/**
* @brief 启动LK电机
*
* @param motor
*/
void LKMotorEnable(LKMotorInstance *motor);
uint8_t LKMotorIsOnline(LKMotorInstance *motor);
#endif // LK_MOTOR_H

View File

@@ -0,0 +1,132 @@
//
// Created by nie_b on 2026/2/23.
//
#ifndef TRONONEH7_SCAFFOLD_MOTOR_DEF_H
#define TRONONEH7_SCAFFOLD_MOTOR_DEF_H
#include "pid.h"
#include "stdint.h"
#define LIMIT_MIN_MAX(x, min, max) (x) = (((x) <= (min)) ? (min) : (((x) >= (max)) ? (max) : (x)))
/**
* @brief 闭环类型,如果需要多个闭环,则使用或运算
* 例如需要速度环和电流环: CURRENT_LOOP|SPEED_LOOP
*/
typedef enum
{
OPEN_LOOP = 0b0000,
CURRENT_LOOP = 0b0001,
SPEED_LOOP = 0b0010,
ANGLE_LOOP = 0b0100,
// only for checking
SPEED_AND_CURRENT_LOOP = 0b0011,
ANGLE_AND_SPEED_LOOP = 0b0110,
ALL_THREE_LOOP = 0b0111,
} Closeloop_Type_e;
typedef enum
{
FEEDFORWARD_NONE = 0b00,
CURRENT_FEEDFORWARD = 0b01,
SPEED_FEEDFORWARD = 0b10,
CURRENT_AND_SPEED_FEEDFORWARD = CURRENT_FEEDFORWARD | SPEED_FEEDFORWARD,
} Feedfoward_Type_e;
/* 反馈来源设定,若设为OTHER_FEED则需要指定数据来源指针,详见Motor_Controller_s*/
typedef enum
{
MOTOR_FEED = 0,
OTHER_FEED,
} Feedback_Source_e;
/* 电机正反转标志 */
typedef enum
{
MOTOR_DIRECTION_NORMAL = 0,
MOTOR_DIRECTION_REVERSE = 1
} Motor_Reverse_Flag_e;
/* 反馈量正反标志 */
typedef enum
{
FEEDBACK_DIRECTION_NORMAL = 0,
FEEDBACK_DIRECTION_REVERSE = 1
} Feedback_Reverse_Flag_e;
typedef enum
{
MOTOR_STOP = 0,
MOTOR_ENALBED = 1,
} Motor_Working_Type_e;
/* 电机控制设置,包括闭环类型,反转标志和反馈来源 */
typedef struct
{
Closeloop_Type_e outer_loop_type; // 最外层的闭环,未设置时默认为最高级的闭环
Closeloop_Type_e close_loop_type; // 使用几个闭环(串级)
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; // 前馈标志
} Motor_Control_Setting_s;
/* 电机控制器,包括其他来源的反馈数据指针,3环控制器和电机的参考输入*/
// 后续增加前馈数据指针
typedef struct
{
float *other_angle_feedback_ptr; // 其他反馈来源的反馈数据指针
float *other_speed_feedback_ptr;
float *speed_feedforward_ptr;
float *current_feedforward_ptr;
PIDInstance current_PID;
PIDInstance speed_PID;
PIDInstance angle_PID;
float pid_ref; // 将会作为每个环的输入和输出顺次通过串级闭环
} Motor_Controller_s;
/* 电机类型枚举 */
typedef enum
{
MOTOR_TYPE_NONE = 0,
GM6020,
M3508,
M2006,
LK9025,
HT04,
} Motor_Type_e;
/**
* @brief 电机控制器初始化结构体,包括三环PID的配置以及两个反馈数据来源指针
* 如果不需要某个控制环,可以不设置对应的pid config
* 需要其他数据来源进行反馈闭环,不仅要设置这里的指针还需要在Motor_Control_Setting_s启用其他数据来源标志
*/
typedef struct
{
float *other_angle_feedback_ptr; // 角度反馈数据指针,注意电机使用total_angle
float *other_speed_feedback_ptr; // 速度反馈数据指针,单位为angle per sec
float *speed_feedforward_ptr; // 速度前馈数据指针
float *current_feedforward_ptr; // 电流前馈数据指针
PID_Init_Config_s current_PID;
PID_Init_Config_s speed_PID;
PID_Init_Config_s angle_PID;
} Motor_Controller_Init_s;
/* 用于初始化CAN电机的结构体,各类电机通用 */
typedef struct
{
Motor_Controller_Init_s controller_param_init_config;
Motor_Control_Setting_s controller_setting_init_config;
Motor_Type_e motor_type;
FDCAN_Init_Config_s fdcan_init_config;
} Motor_Init_Config_s;
#endif // TRONONEH7_SCAFFOLD_MOTOR_DEF_H

View File

@@ -13,7 +13,7 @@ typedef enum
// 添加其他模式
} RobotMode_t; //机器人控制模式
extern RobotMode_t RobotMode;
extern volatile RobotMode_t RobotMode;
// #pragma pack() // 开启字节对齐,结束前面的#pragma pack(1)

View File

@@ -24,6 +24,7 @@ extern "C"
#include "cmsis_os.h"
#include "tim.h"
#include "delayticks.h"
#include "rc.h"
#ifdef __cplusplus
}
@@ -154,6 +155,8 @@ static uint8_t bzply_count = 1; //单个音的节拍延时计数
/*---------------------FUNCTIONS---------------------*/
#define BUZZER_TIM_CLK 5000000U
/***********************************************************************
** 函 数 名: SetBuzzerOff()
** 函数说明: 关闭蜂鸣器
@@ -175,12 +178,9 @@ void SetBuzzerOff(void)
***********************************************************************/
void SetBuzzerFrequence(uint16_t freq)
{
//buzzer --> tim12.channel2
//分频后为1000000Hz
uint16_t period = 1000000 / freq - 1;
uint16_t period = BUZZER_TIM_CLK / freq - 1;
__HAL_TIM_SET_AUTORELOAD(&htim12, period);
__HAL_TIM_SET_COMPARE(&htim12, TIM_CHANNEL_2, period/2);
__HAL_TIM_SET_COMPARE(&htim12, TIM_CHANNEL_2, period / 2);
}
/***********************************************************************
@@ -215,6 +215,11 @@ void buzzer_off(void)
*/
void buzzer_note(uint16_t note, float volume)
{
#ifdef SILENT_MODE
(void)note;
(void)volume;
return;
#endif
if (volume > 1.0f)
{
volume = 1.0f;
@@ -223,17 +228,12 @@ void buzzer_note(uint16_t note, float volume)
{
volume = 0.0f;
}
// 禁用定时器
__HAL_TIM_DISABLE(&htim12);
// 重置定时器计数器
htim12.Instance->CNT = 0;
// 设置自动重装载寄存器ARR以控制PWM信号的频率
htim12.Instance->ARR = (1000000 / note - 1) * 1u;
// 设置比较寄存器CCR3以控制PWM信号的占空比
htim12.Instance->CCR3 = (8 * 10500 / note - 1) * volume * 1u;
// 重新启用定时器
uint32_t arr = BUZZER_TIM_CLK / note - 1;
htim12.Instance->ARR = arr;
htim12.Instance->CCR2 = (uint32_t) ((float) arr * volume * 0.5f);
__HAL_TIM_ENABLE(&htim12);
// 启动PWM信号
HAL_TIM_PWM_Start(&htim12, TIM_CHANNEL_2);
}
@@ -245,78 +245,29 @@ void buzzer_note(uint16_t note, float volume)
*/
void systemstart_song(void)
{
// 播放歌曲的旋律,每个音符后面都跟随一个延时
// buzzer_note(50,0.5);
// delay_ticks(450);
// buzzer_note(53,0.5);
// delay_ticks(450);
// buzzer_note(60,0.5);
// delay_ticks(750);
// buzzer_note(50,0.5);
// delay_ticks(250);
// buzzer_note(60,0.5);
// delay_ticks(250);
// buzzer_note(50,0.5);
// delay_ticks(290);
// buzzer_note(60,0.5);
// delay_ticks(300);
// buzzer_note(75,0.5);
// delay_ticks(550);
// buzzer_note(80,0.5);
// delay_ticks(1000);
// 播放结束后关闭蜂鸣器
HAL_TIM_PWM_Start(&htim12, TIM_CHANNEL_2);
// Super Mario
// buzzer_note(659, 0.5);
// HAL_Delay(120); // E5
// buzzer_note(659, 0.5);
// HAL_Delay(120); // E5
// buzzer_note(659, 0.5);
// HAL_Delay(250); // E5
// buzzer_off();
// HAL_Delay(80);
// buzzer_note(262, 0.5);
// HAL_Delay(120); // C4
// buzzer_note(659, 0.5);
// HAL_Delay(250); // E5
// buzzer_note(784, 0.5);
// HAL_Delay(400); // G5
// buzzer_off();
// HAL_Delay(150);
// buzzer_note(392, 0.5);
// HAL_Delay(500); // G4
// buzzer_note(85,0.5);//中音mi
// delay_ticks(450);
// buzzer_note(90,0.5);
// delay_ticks(450);
// buzzer_note(100,0.5);
// delay_ticks(750);
// buzzer_note(85,0.5);
// delay_ticks(250);
// buzzer_note(100,0.5);
// delay_ticks(250);
// buzzer_note(85,0.5);
// delay_ticks(290);
// buzzer_note(100,0.5);
// delay_ticks(300);
// buzzer_note(125,0.5);
// delay_ticks(550);
// buzzer_note(132,0.5);
// delay_ticks(1000);
// buzzer_note(135,0.5);//高音mi
// delay_ticks(450);
// buzzer_note(140,0.5);
// delay_ticks(450);
// buzzer_note(160,0.5);
// delay_ticks(750);
// buzzer_note(135,0.5);
// delay_ticks(250);
// buzzer_note(160,0.5);
// delay_ticks(250);
// buzzer_note(135,0.5);
// delay_ticks(290);
// buzzer_note(160,0.5);
// delay_ticks(300);
// buzzer_note(200,0.5);
// delay_ticks(550);
// buzzer_note(210,0.5);
// delay_ticks(1000);
//DJI
// buzzer_note(80, 0.5); //高音do
// HAL_Delay(450);
// buzzer_note(90, 0.5); //高音re
// HAL_Delay(450);
// buzzer_note(120, 0.5); //高音sol
// HAL_Delay(550);
// SongSpring(); //为什么要演奏春日影!!!
// SongLaoda();
SongLaoda();
buzzer_off();
}
@@ -332,40 +283,68 @@ void SongSpring(void) //春日影!!!
}
}
void buzzerTask(void const *argument)
{
(void) argument;
HAL_TIM_PWM_Start(&htim12, TIM_CHANNEL_2);
uint8_t was_online = 0;
for (;;)
{
uint8_t is_online = RemoteControlIsOnline();
if (!is_online)
{
buzzer_note(659, 0.5); // E5
osDelay(250);
buzzer_note(494, 0.5); // B4
osDelay(250);
buzzer_off();
osDelay(500);
was_online = 0;
}
else
{
if (!was_online)
{
buzzer_note(1046, 0.5); // C6
osDelay(300);
buzzer_off();
was_online = 1;
}
osDelay(200);
}
}
}
void SongLaoda(void)
{
// 播放歌曲牢大
// buzzer_note(85,0.5);//中音mi
// delay_ticks(450);
// buzzer_note(90,0.5);
// delay_ticks(450);
buzzer_note(100, 0.5); //so
buzzer_note(494, 0.5); // B4
HAL_Delay(200);
buzzer_note(150, 0.5); //re
buzzer_note(740, 0.5); // F#5
HAL_Delay(200);
buzzer_note(135, 0.5); //do
buzzer_note(659, 0.5); // E5
HAL_Delay(200);
buzzer_note(100, 0.5); //so
buzzer_note(494, 0.5); // B4
HAL_Delay(500);
buzzer_note(135, 0.5); //do
buzzer_note(659, 0.5); // E5
HAL_Delay(170);
buzzer_note(150, 0.5); //re
buzzer_note(740, 0.5); // F#5
HAL_Delay(170);
buzzer_note(165, 0.5); //mi
buzzer_note(831, 0.5); // G#5
HAL_Delay(170);
buzzer_note(150, 0.5); //re
buzzer_note(740, 0.5); // F#5
HAL_Delay(170);
buzzer_note(135, 0.5); //do
buzzer_note(659, 0.5); // E5
HAL_Delay(170);
buzzer_note(150, 0.5); //re
buzzer_note(740, 0.5); // F#5
HAL_Delay(170);
buzzer_note(100, 0.5); //so
buzzer_note(494, 0.5); // B4
HAL_Delay(215);
buzzer_note(150, 0.5); //re
buzzer_note(740, 0.5); // F#5
HAL_Delay(215);
buzzer_note(135, 0.5); //do
buzzer_note(659, 0.5); // E5
HAL_Delay(215);
buzzer_note(100, 0.5); //so
buzzer_note(494, 0.5); // B4
HAL_Delay(215);
// buzzer_note(45,0.5);//do

View File

@@ -8,6 +8,8 @@
// #include "struct_typedef.h"
/*---------------------DEFINES-----------------------*/
//
//#define SILENT_MODE // 取消注释以关闭所有提示音有bug
#define PLAYING_STOP 0
#define PLAYING_INIT_MUSIC 1
@@ -65,5 +67,4 @@ extern void PlayingSong(const uint16_t *song, uint16_t len);
extern void PlayingSound(const uint8_t *sound, uint16_t len);
#endif

View File

@@ -5,50 +5,51 @@
#include <stdlib.h>
#include <string.h>
static PowerMeterInstance *power_meter_instance = NULL;
#include "daemon.h"
static XidiPowerMeterInstance *power_meter_instance = NULL;
/**
* @brief 功率计数据解码函数
* @param instance FDCAN实例指针
* @param _instance FDCAN实例指针
*
* @note 数据格式:
* DATA[0]: 电流数值低8位
* DATA[1]: 电流数值高8位
* DATA[2]: 电压数值低8位
* DATA[3]: 电压数值高8位
* DATA[4]-DATA[7]: 保留
*
* 电压电流数值均为放大100倍后的整数需要除以100.0得到实际值
* @note 依据实际硬件与官方示例代码 (小端模式):
* DATA[0]: 电低8位
* DATA[1]: 电高8位
* DATA[2]: 电低8位
* DATA[3]: 电高8位
* 功率使用 P = U * I 直接计算
*/
void PowerMeterDecode(FDCANInstance *instance)
void XidiPowerMeterDecode(FDCANInstance *_instance)
{
if (power_meter_instance == NULL)
return;
uint8_t *rxbuff = instance->rx_buff;
PowerMeter_Measure_s *measure = &power_meter_instance->measure;
uint8_t *rxbuff = _instance->rx_buff;
XidiPowerMeter_Msg_s *measure = &power_meter_instance->powermeter_msg;
// 重载守护进程
// if (power_meter_instance->daemon != NULL)
// {
// DaemonReload(power_meter_instance->daemon);
// }
if (power_meter_instance->daemon != NULL)
{
DaemonReload(power_meter_instance->daemon);
}
// 计算时间间隔
measure->dt = DWT_GetDeltaT(&power_meter_instance->feed_cnt);
// 解析电流数据 (放大100倍需要除以100)
auto current_raw = (int16_t) ((rxbuff[1] << 8) | rxbuff[0]);
measure->current = (float) current_raw / 100.0f;
// 解析电压数据 (放大100倍需要除以100)
auto voltage_raw = (int16_t) ((rxbuff[3] << 8) | rxbuff[2]);
// 1. 按照图片解析电压 (小端模式:[1]为高位,[0]为低位)
// 使用 int16_t 强转是为了兼容可能出现的负数波动
int16_t voltage_raw = (int16_t) ((rxbuff[1] << 8) | rxbuff[0]);
measure->voltage = (float) voltage_raw / 100.0f;
// 计算功率
// 2. 按照图片解析电流 (小端模式:[3]为高位,[2]为低位)
int16_t current_raw = (int16_t) ((rxbuff[3] << 8) | rxbuff[2]);
measure->current = (float) current_raw / 100.0f;
// 3. 按照图片直接计算功率 (电压 * 电流)
measure->power = measure->voltage * measure->current;
// 计算累计能量 (功率 × 时间)
// 4. 计算累计能量 (功率 × 时间)
measure->energy += measure->power * measure->dt * 0.001f; // dt单位是ms转换为秒
measure->update_cnt++;
@@ -58,57 +59,51 @@ void PowerMeterDecode(FDCANInstance *instance)
* @brief 功率计丢失回调函数
* @param power_meter_ptr 功率计实例指针
*/
void PowerMeterLostCallback(void *power_meter_ptr)
void XidiPowerMeterLostCallback(void *power_meter_ptr)
{
PowerMeterInstance *power_meter = (PowerMeterInstance *) power_meter_ptr;
XidiPowerMeterInstance *power_meter = (XidiPowerMeterInstance *) power_meter_ptr;
}
/**
* @brief 功率计初始化
* @param can_handle FDCAN句柄指针
* @return PowerMeterInstance* 功率计实例指针
* @param XidiPowerMeter_config 功率计初始化配置结构体指针 (必须确保其生命周期是全局或静态的)
* @return XidiPowerMeterInstance* 功率计实例指针
*/
PowerMeterInstance *PowerMeterInit(FDCAN_HandleTypeDef *can_handle)
XidiPowerMeterInstance *XidiPowerMeterInit(XidiPowerMeter_Init_Config_s *XidiPowerMeter_config)
{
// 检查是否已经初始化
// 1. 检查是否已经初始化
if (power_meter_instance != NULL)
{
return power_meter_instance;
}
// 分配内存
power_meter_instance = (PowerMeterInstance *) malloc(sizeof(PowerMeterInstance));
// 2. 分配内存并清零
power_meter_instance = (XidiPowerMeterInstance *) malloc(sizeof(XidiPowerMeterInstance));
if (power_meter_instance == NULL)
{
return NULL;
}
memset(power_meter_instance, 0, sizeof(PowerMeterInstance));
memset(power_meter_instance, 0, sizeof(XidiPowerMeterInstance));
// 配置FDCAN初始化参数 - 完全适配您的FDCAN驱动
FDCAN_Init_Config_s can_config = {
.can_handle = can_handle,
.rx_id = 0x213, // 功率计反馈标识符
.tx_id = 0x000, // 不需要发送设为0
.can_module_callback = PowerMeterDecode,
.id = power_meter_instance, // 将实例指针作为ID传递
};
// 3. 配置并注册 FDCAN 实例
// (rx_id, tx_id, can_handle 等参数由外部 config 提供,这里只绑定回调和私有指针)
XidiPowerMeter_config->can_config.can_module_callback = XidiPowerMeterDecode;
XidiPowerMeter_config->can_config.id = power_meter_instance;
// 注册FDCAN实例 - 使用FDCANRegister函数
power_meter_instance->can_instance = FDCANRegister(&can_config);
if (power_meter_instance->can_instance == NULL)
power_meter_instance->fdcan_ins = CANRegister(&XidiPowerMeter_config->can_config);
if (power_meter_instance->fdcan_ins == NULL)
{
free(power_meter_instance);
power_meter_instance = NULL;
return NULL;
}
// 注册守护进程
// Daemon_Init_Config_s daemon_config = {
// .callback = PowerMeterLostCallback,
// .owner_id = power_meter_instance,
// .reload_count = 5, // 50ms未收到数据则认为丢失 (1000Hz发送5个周期)
// };
// power_meter_instance->daemon = DaemonRegister(&daemon_config);
// 4. 配置并注册 守护进程
// (reload_count 等参数由外部 config 提供,这里只绑定回调和私有指针)
XidiPowerMeter_config->daemon_config.callback = XidiPowerMeterLostCallback;
XidiPowerMeter_config->daemon_config.owner_id = power_meter_instance;
power_meter_instance->daemon = DaemonRegister(&XidiPowerMeter_config->daemon_config);
return power_meter_instance;
}
@@ -118,11 +113,11 @@ PowerMeterInstance *PowerMeterInit(FDCAN_HandleTypeDef *can_handle)
* @param power_meter 功率计实例指针
* @return float 电压值 (V)
*/
float PowerMeterGetVoltage(PowerMeterInstance *power_meter)
float XidiPowerMeterGetVoltage(XidiPowerMeterInstance *power_meter)
{
if (power_meter == NULL)
return 0.0f;
return power_meter->measure.voltage;
return power_meter->powermeter_msg.voltage;
}
/**
@@ -130,11 +125,11 @@ float PowerMeterGetVoltage(PowerMeterInstance *power_meter)
* @param power_meter 功率计实例指针
* @return float 电流值 (A)
*/
float PowerMeterGetCurrent(PowerMeterInstance *power_meter)
float XidiPowerMeterGetCurrent(XidiPowerMeterInstance *power_meter)
{
if (power_meter == NULL)
return 0.0f;
return power_meter->measure.current;
return power_meter->powermeter_msg.current;
}
/**
@@ -142,11 +137,11 @@ float PowerMeterGetCurrent(PowerMeterInstance *power_meter)
* @param power_meter 功率计实例指针
* @return float 功率值 (W)
*/
float PowerMeterGetPower(PowerMeterInstance *power_meter)
float XidiPowerMeterGetPower(XidiPowerMeterInstance *power_meter)
{
if (power_meter == NULL)
return 0.0f;
return power_meter->measure.power;
return power_meter->powermeter_msg.power;
}
/**
@@ -154,22 +149,22 @@ float PowerMeterGetPower(PowerMeterInstance *power_meter)
* @param power_meter 功率计实例指针
* @return float 累计能量 (J)
*/
float PowerMeterGetEnergy(PowerMeterInstance *power_meter)
float XidiPowerMeterGetEnergy(XidiPowerMeterInstance *power_meter)
{
if (power_meter == NULL)
return 0.0f;
return power_meter->measure.energy;
return power_meter->powermeter_msg.energy;
}
/**
* @brief 重置累计能量
* @param power_meter 功率计实例指针
*/
void PowerMeterResetEnergy(PowerMeterInstance *power_meter)
void XidiPowerMeterResetEnergy(XidiPowerMeterInstance *power_meter)
{
if (power_meter != NULL)
{
power_meter->measure.energy = 0.0f;
power_meter->powermeter_msg.energy = 0.0f;
}
}
@@ -177,7 +172,7 @@ void PowerMeterResetEnergy(PowerMeterInstance *power_meter)
* @brief 获取功率计实例(用于外部访问)
* @return PowerMeterInstance* 功率计实例指针
*/
PowerMeterInstance *GetPowerMeterInstance(void)
XidiPowerMeterInstance *GetXidiPowerMeterInstance(void)
{
return power_meter_instance;
}

View File

@@ -5,9 +5,10 @@
#ifndef TRONONEH7_SCAFFOLD_XIDIPWMETER_H
#define TRONONEH7_SCAFFOLD_XIDIPWMETER_H
#include "general_def.h"
#include "bsp_dwt.h"
#include "bsp_fdcan.h"
#include "daemon.h"
#include "general_def.h"
/* 功率计测量数据结构 */
typedef struct
@@ -18,34 +19,40 @@ typedef struct
float energy; // 累计能量 (J)
uint32_t update_cnt; // 更新计数器
float dt; // 更新时间间隔
} PowerMeter_Measure_s;
} XidiPowerMeter_Msg_s;
/* 功率计实例结构 */
typedef struct
{
FDCANInstance *can_instance; // FDCAN实例指针
PowerMeter_Measure_s measure; // 测量数据
// DaemonInstance *daemon; // 守护进程
FDCANInstance *fdcan_ins; // FDCAN实例指针
XidiPowerMeter_Msg_s powermeter_msg; // 测量数据
Daemon_Instance *daemon; // 守护进程
uint32_t feed_cnt; // 喂狗计数器
} PowerMeterInstance;
} XidiPowerMeterInstance;
/* 功率计初始化配置 */
typedef struct {
FDCAN_Init_Config_s can_config;
Daemon_Init_Config_s daemon_config;
} XidiPowerMeter_Init_Config_s;
/* 函数声明 */
PowerMeterInstance *PowerMeterInit(FDCAN_HandleTypeDef *can_handle);
XidiPowerMeterInstance *XidiPowerMeterInit(XidiPowerMeter_Init_Config_s *XidiPowerMeter_config);
void PowerMeterDecode(FDCANInstance *instance);
void XidiPowerMeterDecode(FDCANInstance *_instance);
void PowerMeterLostCallback(void *power_meter_ptr);
void XidiPowerMeterLostCallback(void *power_meter_ptr);
float PowerMeterGetVoltage(PowerMeterInstance *power_meter);
float XidiPowerMeterGetVoltage(XidiPowerMeterInstance *power_meter);
float PowerMeterGetCurrent(PowerMeterInstance *power_meter);
float XidiPowerMeterGetCurrent(XidiPowerMeterInstance *power_meter);
float PowerMeterGetPower(PowerMeterInstance *power_meter);
float XidiPowerMeterGetPower(XidiPowerMeterInstance *power_meter);
float PowerMeterGetEnergy(PowerMeterInstance *power_meter);
float XidiPowerMeterGetEnergy(XidiPowerMeterInstance *power_meter);
void PowerMeterResetEnergy(PowerMeterInstance *power_meter);
void XidiPowerMeterResetEnergy(XidiPowerMeterInstance *power_meter);
PowerMeterInstance *GetPowerMeterInstance(void);
XidiPowerMeterInstance *GetPowerMeterInstance(void);
#endif //TRONONEH7_SCAFFOLD_XIDIPWMETER_H

View File

@@ -77,23 +77,31 @@
#define REMOTE_CONTROL_FRAME_SIZE 25u // 遥控器接收的buffer大小
//@todo测试define
#define REMOTE_FS_I6X
// 定义SBUS协议的起始标志
#define SBUS_HEAD 0X0F
// 定义SBUS协议的结束标志
#define SBUS_END 0X00
#define SBUS_FLAG_FRAME_LOST (1u << 2)
#define SBUS_FLAG_FAILSAFE (1u << 3)
// 遥控器数据
static RC_ctrl_t rc_ctrl[2]; //[0]:当前数据TEMP,[1]:上一次的数据LAST.用于按键持续按下和切换的判断
static uint8_t rc_init_flag = 0; // 遥控器初始化标志位
static volatile uint8_t rc_data_valid = 0;
static volatile uint32_t rc_valid_frame_count = 0;
static volatile uint32_t rc_invalid_frame_count = 0;
static volatile uint32_t rc_failsafe_count = 0;
// 遥控器拥有的串口实例,因为遥控器是单例,所以这里只有一个,就不封装了
static USART_Instance *rc_usart_instance;
static Daemon_Instance *rc_daemon_instance;
static uint8_t SBusEndByteIsValid(uint8_t value)
{
return value == SBUS_END || value == 0x04u || value == 0x14u || value == 0x24u || value == 0x34u;
}
#ifdef REMOTE_DJI_DT7
/**
@@ -112,8 +120,11 @@ static void RectifyRCjoystick()
*
* @param sbus_buf 接收buffer
*/
static void sbus_to_rc(const uint8_t *sbus_buf)
static uint8_t sbus_to_rc(const uint8_t *sbus_buf, uint16_t size)
{
if (size != 18u)
return 0;
// 摇杆,直接解算时减去偏置
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;
@@ -169,6 +180,7 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
}
memcpy(&rc_ctrl[LAST], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断
return 1;
}
#endif
@@ -178,7 +190,7 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
*
* @param sbus_buf 接收buffer
*/
static void sbus_to_rc(const uint8_t *sbus_buf)
static uint8_t sbus_to_rc(const uint8_t *sbus_buf, uint16_t size)
{
// // 摇杆,直接解算时减去偏置
// rc_ctrl[TEMP].rc.rocker_r_ = ((sbus_buf[0] | (sbus_buf[1] << 8)) & 0x07ff) - RC_CH_VALUE_OFFSET; //!< Channel 0
@@ -230,8 +242,11 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
// rc_ctrl[TEMP].key_count[KEY_PRESS_WITH_SHIFT][i]++;
// }
if ((sbus_buf[0] != SBUS_HEAD) || (sbus_buf[24] != SBUS_END))
return;
if (size != REMOTE_CONTROL_FRAME_SIZE || sbus_buf[0] != SBUS_HEAD || !SBusEndByteIsValid(sbus_buf[24]))
return 0;
if ((sbus_buf[23] & (SBUS_FLAG_FRAME_LOST | SBUS_FLAG_FAILSAFE)) != 0u)
return 0;
// if (sbus_buf[23] == 0x0C)
// rc_ctrl->online = 0;
@@ -261,20 +276,18 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
// 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)
if (rc_ctrl[TEMP].rc.ch[5] == 0x00F0)
{
rc_ctrl[TEMP].sw_b = RC_SW_UP;
}
else if (rc_ctrl[TEMP].rc.ch[6] == 0x0400)
else if (rc_ctrl[TEMP].rc.ch[5] == 0x0400)
{
rc_ctrl[TEMP].sw_b = RC_SW_MID;
}
else if (rc_ctrl[TEMP].rc.ch[6] == 0x070F)
else if (rc_ctrl[TEMP].rc.ch[5] == 0x070F)
{
rc_ctrl[TEMP].sw_b = RC_SW_DOWN;
}
@@ -283,27 +296,14 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
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_midtoup_flag =
rc_ctrl[TEMP].sw_b_last == RC_SW_MID && rc_ctrl[TEMP].sw_b == RC_SW_UP;
rc_ctrl[TEMP].sw_b_uptomid_flag =
rc_ctrl[TEMP].sw_b_last == RC_SW_UP && rc_ctrl[TEMP].sw_b == RC_SW_MID;
rc_ctrl[TEMP].sw_b_midtodown_flag =
rc_ctrl[TEMP].sw_b_last == RC_SW_MID && rc_ctrl[TEMP].sw_b == RC_SW_DOWN;
rc_ctrl[TEMP].sw_b_downtomid_flag =
rc_ctrl[TEMP].sw_b_last == RC_SW_DOWN && rc_ctrl[TEMP].sw_b == RC_SW_MID;
rc_ctrl[TEMP].sw_b_last = rc_ctrl[TEMP].sw_b;
// 解析SWC状态
@@ -324,46 +324,22 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
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_midtoup_flag =
rc_ctrl[TEMP].sw_c_last == RC_SW_MID && rc_ctrl[TEMP].sw_c == RC_SW_UP;
rc_ctrl[TEMP].sw_c_uptomid_flag =
rc_ctrl[TEMP].sw_c_last == RC_SW_UP && rc_ctrl[TEMP].sw_c == RC_SW_MID;
rc_ctrl[TEMP].sw_c_midtodown_flag =
rc_ctrl[TEMP].sw_c_last == RC_SW_MID && rc_ctrl[TEMP].sw_c == RC_SW_DOWN;
rc_ctrl[TEMP].sw_c_downtomid_flag =
rc_ctrl[TEMP].sw_c_last == RC_SW_DOWN && rc_ctrl[TEMP].sw_c == RC_SW_MID;
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;
}
// SWA和SWD的下降沿
rc_ctrl[TEMP].sw_a_up_to_down_flag =
rc_ctrl[TEMP].sw_a_last == RC_SW_UP && rc_ctrl[TEMP].sw_a == RC_SW_DOWN;
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_up_to_down_flag =
rc_ctrl[TEMP].sw_d_last == RC_SW_UP && rc_ctrl[TEMP].sw_d == RC_SW_DOWN;
rc_ctrl[TEMP].sw_d_last = rc_ctrl[TEMP].sw_d;
// 修正 ch1~ch4
@@ -374,17 +350,54 @@ static void sbus_to_rc(const uint8_t *sbus_buf)
// ...existing code...
memcpy(&rc_ctrl[LAST], &rc_ctrl[TEMP], sizeof(RC_ctrl_t)); // 保存上一次的数据,用于按键持续按下和切换的判断
return 1;
}
#endif
static void RemoteControlPublishOffline(uint8_t force)
{
uint32_t primask = __get_PRIMASK();
__disable_irq();
uint8_t daemon_expired = rc_daemon_instance != NULL && rc_daemon_instance->temp_count == 0;
if (force || daemon_expired)
{
rc_data_valid = 0;
memset(rc_ctrl, 0, sizeof(rc_ctrl));
if (RobotMode != SYS_ERROR_OCCURRED)
RobotMode = REMOTE_NOT_CONNECTED;
}
__set_PRIMASK(primask);
}
/**
* @brief 对sbus_to_rc的简单封装,用于注册到bsp_usart的回调函数中
*
*/
static void RemoteControlRxCallback()
static void RemoteControlRxCallback(USART_Instance *instance, const uint8_t *recv_data, uint16_t recv_size)
{
sbus_to_rc(rc_usart_instance->recv_buff); // 进行协议解析
DaemonReload(rc_daemon_instance); // 先喂狗
(void) instance;
if (recv_size == REMOTE_CONTROL_FRAME_SIZE && recv_data[0] == SBUS_HEAD &&
SBusEndByteIsValid(recv_data[24]) && (recv_data[23] & SBUS_FLAG_FAILSAFE) != 0u)
{
rc_failsafe_count++;
RemoteControlPublishOffline(1);
return;
}
if (!sbus_to_rc(recv_data, recv_size))
{
rc_invalid_frame_count++;
return;
}
rc_valid_frame_count++;
rc_data_valid = 1;
DaemonReload(rc_daemon_instance);
if (RobotMode == REMOTE_NOT_CONNECTED)
RobotMode = REMOTE_CONNECTED;
}
/**
@@ -393,38 +406,74 @@ static void RemoteControlRxCallback()
*/
static void RCLostCallback(void *id)
{
memset(rc_ctrl, 0, sizeof(rc_ctrl)); // 清空遥控器数据
USARTServiceInit(rc_usart_instance); // 尝试重新启动接收
RobotMode = REMOTE_NOT_CONNECTED;
(void) id;
RemoteControlPublishOffline(0);
// 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;
if (rc_init_flag)
return &rc_ctrl[TEMP];
if (rc_usart_handle == NULL || rc_usart_handle->hdmarx == NULL)
return NULL;
rc_usart_instance = USARTRegister(&conf);
memset(rc_ctrl, 0, sizeof(rc_ctrl));
rc_data_valid = 0;
// 进行守护进程的注册,用于定时检查遥控器是否正常工作
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);
if (rc_usart_instance == NULL)
{
USART_Init_Config_s conf = {
.module_callback = RemoteControlRxCallback,
.usart_handle = rc_usart_handle,
.recv_buff_size = REMOTE_CONTROL_FRAME_SIZE,
};
rc_usart_instance = USARTRegister(&conf);
if (rc_usart_instance == NULL)
return NULL;
}
if (rc_daemon_instance == NULL)
{
// 100ms未收到有效数据视为离线,SBUS接收频率约70Hz.
Daemon_Init_Config_s daemon_conf = {
.reload_count = 10,
.callback = RCLostCallback,
.owner_id = NULL,
};
rc_daemon_instance = DaemonRegister(&daemon_conf);
}
rc_init_flag = 1;
return rc_ctrl;
if (USARTServiceInit(rc_usart_instance) != HAL_OK)
rc_usart_instance->rx_restart_error_count++;
return &rc_ctrl[TEMP];
}
uint8_t RemoteControlIsOnline()
uint8_t RemoteControlReadSnapshot(RC_ctrl_t *out)
{
if (rc_init_flag)
return DaemonIsOnline(rc_daemon_instance);
return 0;
if (out == NULL)
return 0;
uint32_t primask = __get_PRIMASK();
__disable_irq();
uint8_t valid = rc_init_flag && rc_data_valid && rc_daemon_instance != NULL &&
rc_daemon_instance->temp_count > 0;
if (valid)
memcpy(out, &rc_ctrl[TEMP], sizeof(*out));
__set_PRIMASK(primask);
return valid;
}
uint8_t RemoteControlIsOnline(void)
{
uint32_t primask = __get_PRIMASK();
__disable_irq();
uint8_t online = rc_init_flag && rc_data_valid && rc_daemon_instance != NULL &&
rc_daemon_instance->temp_count > 0;
__set_PRIMASK(primask);
return online;
}

View File

@@ -125,21 +125,30 @@ typedef struct
uint8_t key_count[3][16];
} RC_ctrl_t;
#endif // FSI6X
/* ------------------------- Internal Data ----------------------------------- */
/**
* @brief 初始化遥控器,该函数会将遥控器注册到串口
*
* @attention 注意分配正确的串口硬件,遥控器在C板上使用USART3
* @attention 当前板级配置使用UART5/PD2接收SBUS
*
*/
RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle);
/**
* @brief 原子复制当前遥控器数据
*
* @param out 接收遥控器数据快照
* @return uint8_t 1:快照有效 0:未初始化、离线或参数无效
*/
uint8_t RemoteControlReadSnapshot(RC_ctrl_t *out);
/**
* @brief 检查遥控器是否在线,若尚未初始化也视为离线
*
* @return uint8_t 1:在线 0:离线
*/
uint8_t RemoteControlIsOnline();
uint8_t RemoteControlIsOnline(void);
#endif // REMOTE_H

View File

@@ -1,5 +1,158 @@
//
// Created by ASUS on 2025/11/17.
//
#include "btb.h"
#include <string.h>
#include <stdlib.h>
// #include "crc8.h"
#include "bsp_dwt.h"
#include "bsp_fdcan.h"
#include "daemon.h"
// 内部函数声明
static void btb_reset_rx(btb_instance_t *ins);
static void btb_rx_callback(FDCANInstance *_instance);
static void btb_lost_callback(void *btb_ins);
/**
* @brief 重置接收状态和缓冲区
*/
static void btb_reset_rx(btb_instance_t *ins)
{
memset(ins->raw_recvbuf, 0, ins->cur_recv_len);
ins->recv_state = 0;
ins->cur_recv_len = 0;
}
/**
* @brief CAN 接收回调(由底层驱动在收到数据时调用)
*/
static void btb_rx_callback(FDCANInstance *_instance)
{
// 从 CANInstance 的 id 字段获取 btb_instance_t 指针
btb_instance_t *comm = (btb_instance_t *)_instance->id;
/* 接收状态机 */
if (_instance->rx_buff[0] == BTB_HEADER && comm->recv_state == 0)
{
if (_instance->rx_buff[1] == comm->recv_data_len)
{
comm->recv_state = 1; // 开始接收数据
}
else
return; // 长度不匹配,忽略
}
if (comm->recv_state)
{
// 检查缓冲区是否溢出
if (comm->cur_recv_len + _instance->rx_len > comm->recv_buf_len)
{
btb_reset_rx(comm);
return;
}
// 拷贝数据
memcpy(comm->raw_recvbuf + comm->cur_recv_len, _instance->rx_buff, _instance->rx_len);
comm->cur_recv_len += _instance->rx_len;
// 检查是否接收完整一帧
if (comm->cur_recv_len == comm->recv_buf_len)
{
// 验证帧尾和 CRC
if (comm->raw_recvbuf[comm->recv_buf_len - 1] == BTB_TAIL)
{
// uint8_t crc = crc_8(comm->raw_recvbuf + 2, comm->recv_data_len); //Todo:CRC
// if (comm->raw_recvbuf[comm->recv_buf_len - 2] == crc)
// {
memcpy(comm->unpacked_recv_data, comm->raw_recvbuf + 2, comm->recv_data_len);
comm->update_flag = 1;
DaemonReload(comm->comm_daemon); // 喂看门狗
// }
}
btb_reset_rx(comm);
}
}
}
/**
* @brief 看门狗超时回调(通信丢失)
*/
static void btb_lost_callback(void *btb_ins)
{
btb_instance_t *comm = (btb_instance_t *)btb_ins;
btb_reset_rx(comm);
LOGWARNING("[btb] can comm rx[%d] lost, reset rx state.", &comm->fdcan_ins->rx_id);
}
/**
* @brief 初始化 BTB 实例
*/
btb_instance_t *btb_init(btb_config_t *config)
{
btb_instance_t *ins = (btb_instance_t *)malloc(sizeof(btb_instance_t));
memset(ins, 0, sizeof(btb_instance_t));
ins->recv_data_len = config->recv_data_len;
ins->recv_buf_len = config->recv_data_len + BTB_OFFSET_BYTES;
ins->send_data_len = config->send_data_len;
ins->send_buf_len = config->send_data_len + BTB_OFFSET_BYTES;
// 预填充发送缓冲区固定字段
ins->raw_sendbuf[0] = BTB_HEADER;
ins->raw_sendbuf[1] = config->send_data_len; // 数据长度
ins->raw_sendbuf[config->send_data_len + BTB_OFFSET_BYTES - 1] = BTB_TAIL;
// 注册 CAN 实例
config->can_config.id = ins;
config->can_config.can_module_callback = btb_rx_callback;
ins->fdcan_ins = CANRegister(&config->can_config); // 假设此函数存在
// 注册看门狗
Daemon_Init_Config_s daemon_config = {
.callback = btb_lost_callback,
.owner_id = (void *)ins,
.reload_count = config->daemon_count,
};
ins->comm_daemon = DaemonRegister(&daemon_config);
return ins;
}
/**
* @brief 发送数据(自动分包)
*/
void btb_send(btb_instance_t *ins, uint8_t *data)
{
// 计算 CRC 并填入发送缓冲区
memcpy(ins->raw_sendbuf + 2, data, ins->send_data_len);//ToDo:CRC
// uint8_t crc = crc_8(data, ins->send_data_len);
// ins->raw_sendbuf[2 + ins->send_data_len] = crc;
// 分包发送CAN 单包最大 8 字节)
uint8_t remain = ins->send_buf_len;
uint8_t offset = 0;
while (remain > 0)
{
uint8_t len = (remain >= 8) ? 8 : remain;
CANSetDLC(ins->fdcan_ins, len); // 设置 DLC
memcpy(ins->fdcan_ins->tx_buff, ins->raw_sendbuf + offset, len);
CANTransmit(ins->fdcan_ins, 1); // 阻塞发送
offset += len;
remain -= len;
}
}
/**
* @brief 获取最新接收的数据(读取后清除更新标志)
*/
void *btb_get(btb_instance_t *ins)
{
ins->update_flag = 0;
return ins->unpacked_recv_data;
}
/**
* @brief 检查通信是否在线(看门狗未超时)
*/
uint8_t btb_is_online(btb_instance_t *ins)
{
return DaemonIsOnline(ins->comm_daemon);
}

View File

@@ -5,4 +5,82 @@
#ifndef TRONONEH7_SCAFFOLD_BTB_H
#define TRONONEH7_SCAFFOLD_BTB_H
#include "bsp_fdcan.h"
#include "daemon.h"
#define BTB_MAX_INSTANCE 4 // 注意均衡负载,一条总线上不要挂载过多的外设
#define BTB_MAX_BUFFSIZE 60 // 最大发送/接收字节数,如果不够可以增加此数值
#define BTB_HEADER 's' // 帧头
#define BTB_TAIL 'e' // 帧尾
#define BTB_OFFSET_BYTES 4 // 's'+ datalen + 'e' + crc8
#pragma pack(1)
/* BTB 结构体, 拥有 BTB 的 app 应该包含一个 BTB 指针 */
typedef struct
{
FDCANInstance *fdcan_ins;
/* 发送部分 */
uint8_t send_data_len; // 发送数据长度
uint8_t send_buf_len; // 发送缓冲区长度,为发送数据长度+帧头单包数据长度帧尾以及校验和(4)
uint8_t raw_sendbuf[BTB_MAX_BUFFSIZE + BTB_OFFSET_BYTES]; // 额外4个bytes保存帧头帧尾和校验和
/* 接收部分 */
uint8_t recv_data_len; // 接收数据长度
uint8_t recv_buf_len; // 接收缓冲区长度,为接收数据长度+帧头单包数据长度帧尾以及校验和(4)
uint8_t raw_recvbuf[BTB_MAX_BUFFSIZE + BTB_OFFSET_BYTES]; // 额外4个bytes保存帧头帧尾和校验和
uint8_t unpacked_recv_data[BTB_MAX_BUFFSIZE]; // 解包后的数据,调用 btb_get() 后 cast 成对应的类型通过指针读取即可
/* 接收和更新标志位*/
uint8_t recv_state; // 接收状态,
uint8_t cur_recv_len; // 当前已经接收到的数据长度(包括帧头帧尾 datalen 和校验和)
uint8_t update_flag; // 数据更新标志位,当接收到新数据时,会将此标志位置1,调用 btb_get() 后会将此标志位置0
Daemon_Instance *comm_daemon;
} btb_instance_t;
#pragma pack()
/* BTB 初始化结构体 */
typedef struct
{
FDCAN_Init_Config_s can_config; // CAN初始化结构体
uint8_t send_data_len; // 发送数据长度
uint8_t recv_data_len; // 接收数据长度
uint16_t daemon_count; // 守护进程计数,用于初始化守护进程
} btb_config_t;
/**
* @brief 初始化 BTB
*
* @param config BTB 初始化结构体
* @return btb_instance_t*
*/
btb_instance_t *btb_init(btb_config_t *config);
/**
* @brief 通过 BTB 发送数据
*
* @param instance btb 实例
* @param data 注意此地址的有效数据长度需要和初始化时传入的 datalen 相同
*/
void btb_send(btb_instance_t *instance, uint8_t *data);
/**
* @brief 获取 BTB 接收的数据,需要自己使用强制类型转换将返回的 void 指针转换成指定类型
*
* @return void* 返回的数据指针
* @attention 注意如果希望直接通过转换指针访问数据,如果数据是 union 或 struct,要检查是否使用了 pack(n)
* BTB 接收到的数据可以看作是 pack(1) 之后的,是连续存放的.
* 如果使用了 pack(n) 可能会导致数据错乱,并且无法使用强制类型转换通过 memcpy 直接访问,转而需要手动解包.
* 强烈建议通过 BTB 传输的数据使用 pack(1)
*/
void *btb_get(btb_instance_t *instance);
/**
* @brief 检查 BTB 是否在线
*
* @param instance
* @return uint8_t
*/
uint8_t btb_is_online(btb_instance_t *instance);
#endif //TRONONEH7_SCAFFOLD_BTB_H

View File

@@ -1,7 +1,9 @@
#include "daemon.h"
#include "bsp_dwt.h"
#include "stdlib.h"
#include "cmsis_os2.h"
#include "main.h"
#include "memory.h"
#include "stdlib.h"
/* 用于保存所有的daemon instance */
static Daemon_Instance *daemon_instances[DAEMON_MAX_NUM];
@@ -15,8 +17,7 @@ Daemon_Instance *DaemonRegister(Daemon_Init_Config_s *config)
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_instance->temp_count = config->init_count == 0 ? daemon_instance->reload_count : config->init_count;
daemon_instances[idx++] = daemon_instance;
@@ -30,7 +31,13 @@ Daemon_Instance *DaemonRegister(Daemon_Init_Config_s *config)
*/
void DaemonReload(Daemon_Instance *daemon)
{
if (daemon == NULL)
return;
uint32_t primask = __get_PRIMASK();
__disable_irq();
daemon->temp_count = daemon->reload_count;
__set_PRIMASK(primask);
}
/**
@@ -41,7 +48,14 @@ void DaemonReload(Daemon_Instance *daemon)
*/
uint8_t DaemonIsOnline(Daemon_Instance *daemon)
{
return daemon->temp_count > 0;
if (daemon == NULL)
return 0;
uint32_t primask = __get_PRIMASK();
__disable_irq();
uint8_t online = daemon->temp_count > 0;
__set_PRIMASK(primask);
return online;
}
/**
@@ -49,16 +63,28 @@ uint8_t DaemonIsOnline(Daemon_Instance *daemon)
* 模块成功接受数据或成功操作则会重载temp_count的值为reload_count.
*
*/
void DaemonTask(void)
// === 在 daemon.c 中修改 ===
/**
* @brief 轮询刷新所有的守护进程实例
*/
void Daemon_Update(void) // 名字改掉,不要叫 Task避免和 RTOS 线程混淆
{
Daemon_Instance *daemon;
for (uint8_t i = 0; i < idx; i++)
{
daemon = daemon_instances[i];
if (daemon->temp_count > 0) // 如果计数器还有值,说明上一次喂狗后还没有超时,则计数器减一
uint8_t expired = 0;
uint32_t primask = __get_PRIMASK();
__disable_irq();
if (daemon->temp_count > 0)
daemon->temp_count--;
else if (daemon->callback != NULL) // 等于零说明超时了,调用回调函数(如果有的话)
else
expired = 1;
__set_PRIMASK(primask);
if (expired && daemon->callback != NULL)
daemon->callback(daemon->id);
// @todo 可以加入蜂鸣器或者led等提示
}
}

View File

@@ -25,7 +25,7 @@ typedef struct daemon_ins
uint16_t reload_count; // 重载值
offline_callback callback; // 离线处理函数,当模块离线时调用
uint16_t temp_count; // 当前值,减为零说明模块离线或异常
volatile uint16_t temp_count; // 当前值,减为零说明模块离线或异常
void *id; // 模块id,用于标识模块,初始化时传入
} Daemon_Instance;
@@ -67,6 +67,6 @@ uint8_t DaemonIsOnline(Daemon_Instance *daemon);
* 模块成功接受数据或成功操作则会重载temp_count的值为reload_count.
*
*/
void DaemonTask(void);
void Daemon_Update(void);
#endif // DAEMON_H

View File

@@ -1,528 +1,57 @@
// //@todo:仅为开发板测试使用cmd.c 后续改进
//
// // app
// #include "robot_def.h"
// #include "dev_cmd.h"
// // module
// #include "rc.h"
// #include "ins_task.h"
// // #include "master_process.h"
// // #include "message_center.h"
// #include "general_def.h"
// // #include "dji_motor.h"
// #include "buzzer.h"
// // bsp
// #include "bsp_dwt.h"
// #include "bsp_log.h"
//
// // 私有宏,自动将编码器转换成角度值
// #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
//
// /* cmd应用包含的模块实例指针和交互信息存储*/
// #ifdef GIMBAL_BOARD // 对双板的兼容,条件编译
// #include "can_comm.h"
// static CANCommInstance *cmd_can_comm; // 双板通信
// #endif
// #ifdef ONE_BOARD
// static Publisher_t *chassis_cmd_pub; // 底盘控制消息发布者
// static Subscriber_t *chassis_feed_sub; // 底盘反馈信息订阅者
// #endif // ONE_BOARD
//
// static Chassis_Ctrl_Cmd_s chassis_cmd_send; // 发送给底盘应用的信息,包括控制信息和UI绘制相关
// static Chassis_Upload_Data_s chassis_fetch_data; // 从底盘应用接收的反馈信息信息,底盘功率枪口热量与底盘运动状态等
//
// static RC_ctrl_t *rc_data; // 遥控器数据,初始化时返回
// static Vision_Recv_s *vision_recv_data; // 视觉接收数据指针,初始化时返回
// static Vision_Send_s vision_send_data; // 视觉发送数据
//
// static Publisher_t *gimbal_cmd_pub; // 云台控制消息发布者
// static Subscriber_t *gimbal_feed_sub; // 云台反馈信息订阅者
// static Gimbal_Ctrl_Cmd_s gimbal_cmd_send; // 传递给云台的控制信息
// static Gimbal_Upload_Data_s gimbal_fetch_data; // 从云台获取的反馈信息
//
// static Publisher_t *shoot_cmd_pub; // 发射控制消息发布者
// static Subscriber_t *shoot_feed_sub; // 发射反馈信息订阅者
// static Shoot_Ctrl_Cmd_s shoot_cmd_send; // 传递给发射的控制信息
// static Shoot_Upload_Data_s shoot_fetch_data; // 从发射获取的反馈信息
//
// static Robot_Status_e robot_state; // 机器人整体工作状态
// static Work_Mode_e vision_work_mode;
// static float chassis_speed_buff;
// static uint8_t EmergencyHandlerflag = 0;
// static gimbal_control_e Gimbal_control;
// static float Gimbal[2];
// static int keyC_flag = 0;
// static int keyC_last_flag = 0;
// static int cap_flag = 0; //用于超电回复死区
//
// static uint16_t chassis_power_robot_level;
// // PITCH轴限位带测定
//
// #define PITCH_MAX 21
// #define PITCH_MIN -19
//
// void RobotCMDInit()
// {
// Gimbal[0] = 0;
// Gimbal[1] = 0; //初始化gimbal调参数组以便Ozone寻地址
// rc_data = RemoteControlInit(&huart3); // 修改为对应串口,注意如果是自研板dbus协议串口需选用添加了反相器的那个
// vision_recv_data = VisionInit(&huart1); // 视觉通信串口
//
// gimbal_cmd_pub = PubRegister("gimbal_cmd", sizeof(Gimbal_Ctrl_Cmd_s));
// gimbal_feed_sub = SubRegister("gimbal_feed", sizeof(Gimbal_Upload_Data_s));
// shoot_cmd_pub = PubRegister("shoot_cmd", sizeof(Shoot_Ctrl_Cmd_s));
// shoot_feed_sub = SubRegister("shoot_feed", sizeof(Shoot_Upload_Data_s));
//
// #ifdef ONE_BOARD // 双板兼容
// chassis_cmd_pub = PubRegister("chassis_cmd", sizeof(Chassis_Ctrl_Cmd_s));
// chassis_feed_sub = SubRegister("chassis_feed", sizeof(Chassis_Upload_Data_s));
// #endif // ONE_BOARD
// #ifdef GIMBAL_BOARD
// CANComm_Init_Config_s comm_conf = {
// .can_config = {
// .can_handle = &hcan1,
// .tx_id = 0x312,
// .rx_id = 0x311,
// },
// .recv_data_len = sizeof(Chassis_Upload_Data_s),
// .send_data_len = sizeof(Chassis_Ctrl_Cmd_s),
// };
// cmd_can_comm = CANCommInit(&comm_conf);
// #endif // GIMBAL_BOARD
// shoot_cmd_send.attack_mode = NORMAL;
// gimbal_cmd_send.pitch = 0;
// gimbal_cmd_send.yaw = 0;
// gimbal_cmd_send.gimbal_mode = GIMBAL_ZERO_FORCE;
// robot_state = ROBOT_READY; // 启动时机器人进入工作模式,后续加入所有应用初始化完成之后再进入
// }
//
// /**
// * @brief 根据gimbal app传回的当前电机角度计算和零位的误差
// * 单圈绝对角度的范围是0~360,说明文档中有图示
// *
// */
// static void CalcOffsetAngle()
// {
// // 别名angle提高可读性,不然太长了不好看,虽然基本不会动这个函数
// static float angle;
// angle = gimbal_fetch_data.yaw_angle; // 从云台获取的当前yaw电机单圈角度
// #if YAW_ECD_GREATER_THAN_4096 // 如果大于180度
// if (angle > YAW_ALIGN_ANGLE)
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE;
// else if (angle <= YAW_ALIGN_ANGLE && angle >= YAW_ALIGN_ANGLE - 180.0f)
// chassis_cmd_send. = angle - YAW_ALIGN_ANGLE;
// else
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE + 360.0f;
// #else // 小于180度
// if (angle > YAW_ALIGN_ANGLE && angle <= 180.0f + YAW_ALIGN_ANGLE)
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE;
// else if (angle > 180.0f + YAW_ALIGN_ANGLE)
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE - 360.0f;
// else
// chassis_cmd_send.offset_angle = angle - YAW_ALIGN_ANGLE;
// #endif
// }
//
// /**
// * @brief 控制输入为遥控器(调试时)的模式和控制量设置
// *
// */
// static void RemoteControlSet()
// {
// chassis_cmd_send.rotate_control = 1.0;
// // 云台软件限位
// if (gimbal_cmd_send.pitch > PITCH_MAX)
// gimbal_cmd_send.pitch = PITCH_MAX;
// else if (gimbal_cmd_send.pitch < PITCH_MIN)
// gimbal_cmd_send.pitch = PITCH_MIN;
//
// // 左侧开关状态为[中]且右侧开关为[下],视觉模式
// if (switch_is_mid(rc_data[TEMP].rc.switch_left) && switch_is_down(rc_data[TEMP].rc.switch_right))
// {
// //gimbal_cmd_send.yaw = (vision_recv_data->yaw == 0 ? gimbal_cmd_send.yaw : -(vision_recv_data->yaw)); // 由于视觉调试时角度给反所以添加负号-H
// //gimbal_cmd_send.pitch = (vision_recv_data->pitch == 0 ? gimbal_cmd_send.pitch : vision_recv_data->pitch);
// // 待添加,视觉会发来和目标的误差,同样将其转化为total angle的增量进行控制
// chassis_cmd_send.chassis_mode = CHASSIS_RE_ROTATE; // 由于24赛季检录需要滑环检测故调整调试视觉时请取消注释
// gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
// // chassis_cmd_send.fly_flag = 1;
// // ...
// }
// if (switch_is_mid(rc_data[TEMP].rc.switch_left) && switch_is_mid(rc_data[TEMP].rc.switch_right))
// {
// chassis_cmd_send.chassis_mode = CHASSIS_NO_FOLLOW;
// gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
// }
// // 左侧开关状态为[下],或视觉未识别到目标,纯遥控器拨杆控制
// if (switch_is_down(rc_data[TEMP].rc.switch_left) || vision_recv_data->target_state == NO_TARGET)
// {
// // 按照摇杆的输出大小进行角度增量,增益系数需调整
// gimbal_cmd_send.yaw += 0.0025f * (float) rc_data[TEMP].rc.rocker_l_;
// gimbal_cmd_send.pitch += 0.001f * (float) rc_data[TEMP].rc.rocker_l1;
// // gimbal_cmd_send.yaw = Gimbal[0];
// // gimbal_cmd_send.pitch = Gimbal[1];
// // Gimbal[]用于gimbal调参阶跃响应需要调参时取消注释
// }
// if (switch_is_down(rc_data[TEMP].rc.switch_left) && switch_is_down(rc_data[TEMP].rc.switch_right))
// // 右侧开关状态[下],底盘小陀螺
// {
// chassis_cmd_send.chassis_mode = CHASSIS_ROTATE_REMOTE; // CHASSIS_ROTATE
// gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
// }
// if (switch_is_down(rc_data[TEMP].rc.switch_left) && switch_is_mid(rc_data[TEMP].rc.switch_right))
// // 右侧开关状态[中],底盘和云台分离,底盘保持不转动
// {
// chassis_cmd_send.chassis_mode = CHASSIS_NO_FOLLOW;
// gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
// }
//
//
// // 底盘参数,目前没有加入小陀螺(调试似乎暂时没有必要),系数需要调整
// chassis_cmd_send.vx = 80.0f * (float) rc_data[TEMP].rc.rocker_r_; // _水平方向
// chassis_cmd_send.vy = 80.0f * (float) rc_data[TEMP].rc.rocker_r1; // 1竖直方向
//
// // 发射参数
//
// // 摩擦轮控制,拨轮向上打为负,向下为正
// if (rc_data[TEMP].rc.dial < -100) // 向上超过100,打开摩擦轮
// 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; // LOAD_BURSTFIRELOAD_1_BULLET
// }
// else
// shoot_cmd_send.load_mode = LOAD_STOP;
// // 射频控制,固定每秒1发,后续可以根据左侧拨轮的值大小切换射频,
// if (rc_data[TEMP].rc.switch_left == 3 && switch_is_down(rc_data[TEMP].rc.switch_right))
// {
// shoot_cmd_send.friction_mode = FRICTION_ON;
// if (vision_recv_data->target_state == READY_TO_FIRE)
// shoot_cmd_send.load_mode = LOAD_BURSTFIRE;
// else
// shoot_cmd_send.load_mode = LOAD_STOP;
// }
// shoot_cmd_send.shoot_rate = 13;
// }
//
// // /**
// // * @brief 紧急停止,包括遥控器左上侧拨轮打满/重要模块离线/双板通信失效等
// // * 停止的阈值'300'待修改成合适的值,或改为开关控制.
// // *
// // * @todo 后续修改为遥控器离线则电机停止(关闭遥控器急停),通过给遥控器模块添加daemon实现
// // *
// // */
// static void EmergencyHandler()
// {
// // 拨轮的向下拨超过一半进入急停模式.注意向打时下拨轮是正
// if (rc_data[TEMP].rc.dial > 300 || robot_state == ROBOT_STOP) // 还需添加重要应用和模块离线的判断
// {
// 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;
// LOGERROR("[CMD] emergency stop!");
// }
// // 遥控器右侧开关为[上],恢复正常运行
// if (switch_is_up(rc_data[TEMP].rc.switch_right))
// {
// EmergencyHandlerflag = 1;
// }
// if (switch_is_mid(rc_data[TEMP].rc.switch_right) && EmergencyHandlerflag == 1)
// {
// robot_state = ROBOT_READY;
// shoot_cmd_send.shoot_mode = SHOOT_ON;
//
// LOGINFO("[CMD] reinstate, robot ready");
// EmergencyHandlerflag = 0;
// gimbal_cmd_send.yaw = -gimbal_fetch_data.gimbal_imu_data.YawTotalAngle; // 急停时设定值保持与实际值同步,避免恢复时疯转
// gimbal_cmd_send.pitch = 0;
// }
// // 此处为cyx设置将遥控器急停恢复设置为右上然后右中组合以防键鼠控制无法急停等
// // 故不建议在右上添加底盘跟随或小陀螺surprise
// else if (switch_is_up(rc_data[TEMP].rc.switch_left)) // 遥控器左侧开关状态为[上],键盘控制
// {
// 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;
// gimbal_cmd_send.gimbal_mode = GIMBAL_GYRO_MODE;
// 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;
//
// gimbal_cmd_send.yaw = -gimbal_fetch_data.gimbal_imu_data.YawTotalAngle; // 急停时设定值保持与实际值同步,避免恢复时疯转
// gimbal_cmd_send.pitch = 0;
// break;
// }
// }
// }
//
// static void MouseKeySet()
// {
// shoot_cmd_send.shoot_rate = 8;
// shoot_cmd_send.load_mode = LOAD_BURSTFIRE;
// chassis_cmd_send.heat_control = HOLD;
// chassis_cmd_send.rotate_control = 1.0;
// chassis_cmd_send.fly_flag = 0;
// chassis_cmd_send.vy = 0.55 * (rc_data[TEMP].key[KEY_PRESS].w * chassis_speed_buff - rc_data[TEMP].key[KEY_PRESS].s *
// chassis_speed_buff); // 系数待测,平移运动功率限制!
// chassis_cmd_send.vx = 0.55 * (rc_data[TEMP].key[KEY_PRESS].a * chassis_speed_buff - rc_data[TEMP].key[KEY_PRESS].d *
// chassis_speed_buff);
// switch (rc_data[TEMP].key[KEY_PRESS].x) // X键刷新UI
// {
// case 1:
// chassis_cmd_send.ui_mode = UI_REFRESH;
// break;
// default:
// chassis_cmd_send.ui_mode = UI_KEEP;
// break;
// }
//
// switch (rc_data[TEMP].mouse.press_r) // 鼠标右键开启自瞄
// {
// case 0:
// gimbal_cmd_send.yaw += (float) rc_data[TEMP].mouse.x / 660 * 8; // 系数待测
// gimbal_cmd_send.pitch -= (float) rc_data[TEMP].mouse.y / 660 * 8;
// // pitch限位
// if (gimbal_cmd_send.pitch > PITCH_MAX)
// gimbal_cmd_send.pitch = PITCH_MAX;
// else if (gimbal_cmd_send.pitch < PITCH_MIN)
// gimbal_cmd_send.pitch = PITCH_MIN;
// break;
//
// default:
// shoot_cmd_send.shoot_rate = 13;
// if (vision_recv_data->target_state == NO_TARGET)
// {
// gimbal_cmd_send.yaw += (float) rc_data[TEMP].mouse.x / 660 * 8; // 系数待测
// gimbal_cmd_send.pitch -= (float) rc_data[TEMP].mouse.y / 660 * 8;
// }
// else
// {
// gimbal_cmd_send.yaw = -(vision_recv_data->yaw == 0 ? gimbal_cmd_send.yaw : vision_recv_data->yaw);
// gimbal_cmd_send.pitch = (vision_recv_data->pitch == 0
// ? gimbal_cmd_send.pitch
// : vision_recv_data->pitch);
// }
// // 视觉状态
// if (vision_recv_data->target_state == NO_TARGET)
// chassis_cmd_send.vision_mode = UNLOCK;
// else if (vision_recv_data->target_state == TARGET_CONVERGING)
// chassis_cmd_send.vision_mode = CONVERGE;
// else if (vision_recv_data->target_state == READY_TO_FIRE)
// chassis_cmd_send.vision_mode = LOCK;
// else
// chassis_cmd_send.vision_mode = UNLOCK;
// break;
// }
// switch (rc_data[TEMP].key_count[KEY_PRESS][Key_E] % 2) // E键设置切换发射模式单发/连发
// {
// case 0:
// shoot_cmd_send.load_mode = LOAD_1_BULLET;
// chassis_cmd_send.load_mode = LOAD_1_BULLET;
// break;
// case 1:
// shoot_cmd_send.load_mode = LOAD_BURSTFIRE;
// chassis_cmd_send.load_mode = LOAD_BURSTFIRE;
// break;
// //提前为chassis赋值以在发弹前更新UI
// }
// switch (rc_data[TEMP].key_count[KEY_PRESS][Key_V] % 2) // V键设置切换射频为狂暴模式
// {
// case 0:
// chassis_cmd_send.attack_mode = NORMAL;
// break;
// case 1:
// chassis_cmd_send.attack_mode = VIOLENT;
// chassis_cmd_send.heat_control = FIGHT;
// shoot_cmd_send.shoot_rate = 25;
// break;
// }
// switch (rc_data[TEMP].key_count[KEY_PRESS][Key_Z] % 2) // Z键设置是否接入热量闭环
// {
// case 0:
// break;
// case 1:
// chassis_cmd_send.heat_control = FIGHT;
// break;
// }
// switch (rc_data[TEMP].mouse.press_l) // 鼠标左键射击
// {
// case 0:
// shoot_cmd_send.load_mode = LOAD_STOP;
// break;
// default:
// if (shoot_cmd_send.friction_mode != FRICTION_ON)
// shoot_cmd_send.load_mode = LOAD_STOP; // 摩擦轮不开启则拨盘不转, 防止卡弹
// if (rc_data[TEMP].mouse.press_r && (vision_recv_data->target_state != READY_TO_FIRE))
// {
// shoot_cmd_send.load_mode = LOAD_STOP;
// break;
// }
// if (chassis_fetch_data.over_heat_flag == 1 && chassis_cmd_send.heat_control == HOLD)
// {
// shoot_cmd_send.load_mode = LOAD_STOP;
// break;
// }
// break;
// }
// switch (rc_data[TEMP].key_count[KEY_PRESS][Key_G] % 2) // G键狗洞模式
// {
// case 0:
// chassis_cmd_send.tunnel_mode = TUNNEL_OFF;
// break;
// default:
// gimbal_cmd_send.pitch = 0;
// chassis_cmd_send.tunnel_mode = TUNNEL_ON;
// 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_Q] % 2) // Q键设置底盘运动模式
// {
// case 0:
// chassis_cmd_send.chassis_mode = CHASSIS_FOLLOW_GIMBAL_YAW;
// break;
// default:
// chassis_cmd_send.chassis_mode = CHASSIS_ROTATE;
// break;
// }
// switch (rc_data[TEMP].key_count[KEY_PRESS][Key_R] % 2) // R键设置45度转向
// {
// case 0:
// chassis_cmd_send.fly_flag = 0;
// break;
// default:
// chassis_cmd_send.offset_angle += 45;
// chassis_cmd_send.fly_flag = 1;
// break;
// }
// switch (rc_data[TEMP].key[KEY_PRESS].shift) // 按shift使用超级电容
// {
// case 1:
// chassis_speed_buff = 40000;
// break;
// default:
// chassis_speed_buff = 13000;
// break;
// }
// //建议增加超级电容电压控制如小于12V时降低增速等充至16V恢复等
// //此处为24赛季双板通信出现问题故未修改云台板收不到底盘板反馈信息未解决
//
// //TODO:B键设置功率
// switch (rc_data[TEMP].key_count[KEY_PRESS][Key_B] % 11)
// {
// case 0:
// chassis_power_robot_level = 0;
// break;
// case 1:
// chassis_power_robot_level = 1;
// break;
// case 2:
// chassis_power_robot_level = 2;
// break;
// case 3:
// chassis_power_robot_level = 3;
// break;
// case 4:
// chassis_power_robot_level = 4;
// break;
// case 5:
// chassis_power_robot_level = 5;
// break;
// case 6:
// chassis_power_robot_level = 6;
// break;
// case 7:
// chassis_power_robot_level = 7;
// break;
// case 8:
// chassis_power_robot_level = 8;
// break;
// case 9:
// chassis_power_robot_level = 9;
// break;
// case 10:
// chassis_power_robot_level = 10;
// break;
// default:
// chassis_power_robot_level = 0;
// break;
// }
// // 24hl有点蠢修改等级需要急停
// }
//
//
// /* 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率) */
// void RobotCMDTask()
// {
// chassis_cmd_send.ui_mode = UI_KEEP;
// // 从其他应用获取回传数据
// #ifdef ONE_BOARD
// SubGetMessage(chassis_feed_sub, (void *) &chassis_fetch_data);
// #endif // ONE_BOARD
// #ifdef GIMBAL_BOARD
// chassis_fetch_data = *(Chassis_Upload_Data_s *) CANCommGet(cmd_can_comm);
// #endif // GIMBAL_BOARD
// SubGetMessage(shoot_feed_sub, &shoot_fetch_data);
// SubGetMessage(gimbal_feed_sub, &gimbal_fetch_data);
//
// // 根据gimbal的反馈值计算云台和底盘正方向的夹角,不需要传参,通过static私有变量完成
// CalcOffsetAngle();
// // 根据遥控器左侧开关,确定当前使用的控制模式为遥控器调试还是键鼠
// if (switch_is_up(rc_data[TEMP].rc.switch_left))
// {
// MouseKeySet(); // 调试专用
// }
// else
// {
// RemoteControlSet();
// } // 遥控器左侧开关状态为[上],键盘控制
// EmergencyHandler(); // 处理模块离线和遥控器急停等紧急情况
//
// // 设置视觉发送数据,还需增加加速度和角速度数据
// VisionSetFlag(chassis_fetch_data.self_color, vision_work_mode, 30); //30为弹速
// //顺序为pitchyaw需发送总角度以防出现角度跟随bugroll
// VisionSetAltitude(gimbal_fetch_data.gimbal_imu_data.Pitch, gimbal_fetch_data.gimbal_imu_data.YawTotalAngle,
// gimbal_fetch_data.gimbal_imu_data.Roll);
//
// // 推送消息,双板通信,视觉通信等
// // 其他应用所需的控制数据在remotecontrolsetmode和mousekeysetmode中完成设置
// shoot_cmd_send.bullet_speed = chassis_fetch_data.bullet_speed;
// chassis_cmd_send.friction_mode = shoot_cmd_send.friction_mode;
// chassis_cmd_send.yaw_angle = gimbal_fetch_data.gimbal_imu_data.Yaw;
// chassis_cmd_send.pitch_angle = gimbal_fetch_data.pitch_angle;
// chassis_cmd_send.init_totalangle = shoot_fetch_data.init_totalangle;
// chassis_cmd_send.totalangle = shoot_fetch_data.totalangle;
// chassis_cmd_send.chassis_power_robot_level = chassis_power_robot_level;
//
//
// #ifdef ONE_BOARD
// PubPushMessage(chassis_cmd_pub, (void *) &chassis_cmd_send);
// #endif // ONE_BOARD
// #ifdef GIMBAL_BOARD
// CANCommSend(cmd_can_comm, (void *) &chassis_cmd_send);
// //chassis_cmd_send.yaw_motor_total_round_angle = gimbal_fetch_data.yaw_motor_total_round_angle;
// #endif // GIMBAL_BOARD
// PubPushMessage(shoot_cmd_pub, (void *) &shoot_cmd_send);
// PubPushMessage(gimbal_cmd_pub, (void *) &gimbal_cmd_send);
// }
// ==================== 1. 包含必要的头文件 ====================
#include "dev_cmd.h"
#include "general_def.h"
#include "bsp_log.h"
// 引入你的功率计头文件
#include "cmsis_os2.h"
#include "xidipwmeter.h"
// ==================== 2. 定义局部指针 ====================
static XidiPowerMeterInstance *chassis_power_meter; // 功率计实例指针
// 存放读取到的功率数据(也可以直接放进发给底盘的结构体里)
static float current_power_w = 0.0f;
static float current_voltage_v = 0.0f;
static float current_energy_j = 0.0f;
// ==================== 3. 在初始化函数中注册 ====================
// 1. 定义为静态变量static确保它的内存空间常驻防止底层指针悬垂
static XidiPowerMeter_Init_Config_s xidi_pm_config = {
.can_config = {
.can_handle = &hfdcan2, // 原来直接传的句柄,现在放进结构体里
.rx_id = 0x213, // 接收 ID
.tx_id = 0x000, // 发送 ID (不发送填0)
},
.daemon_config = {
.reload_count = 5, // 守护进程超时周期 (例如5次没收到算离线)
}
};
// 2. 初始化任务
void RobotCMDInit()
{
// 传入配置结构体的地址,而不是直接传 hfdcan1
chassis_power_meter = XidiPowerMeterInit(&xidi_pm_config);
if (chassis_power_meter == NULL)
{
LOGERROR("[CMD] Power Meter Init Failed!");
}
else
{
// 建议加一句成功日志,方便上机调试时确认
LOGINFO("[CMD] Power Meter Init Success!");
}
}
// ==================== 4. 在控制任务中读取使用 ====================
void CmdTask(void *argument)
{
RobotCMDInit();
DaemonReload(chassis_power_meter->daemon);
while (1)
{
osDelay(10); // 根据实际需要调整读取频率
}
}

View File

@@ -1,20 +1,22 @@
// //
// // Created by ASUS on 2025/12/15.
// //
//
// #ifndef TRONONEH7_SCAFFOLD_DEV_CMD_H
// #define TRONONEH7_SCAFFOLD_DEV_CMD_H
// Created by ASUS on 2025/12/15.
//
// /**
// * @brief 机器人核心控制任务初始化,会被RobotInit()调用
// *
// */
// void RobotCMDInit();
//
// /**
// * @brief 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率)
// *
// */
// void RobotCMDTask();
//
// #endif //TRONONEH7_SCAFFOLD_DEV_CMD_H
#ifndef TRONONEH7_SCAFFOLD_DEV_CMD_H
#define TRONONEH7_SCAFFOLD_DEV_CMD_H
// 如果用到了 FreeRTOS 的 API可以在这里包含或者在需要用到的源文件里包含
// #include "cmsis_os2.h"
/**
* @brief 机器人指令控制及外设初始化
*/
void RobotCMDInit(void);
/**
* @brief 指令下发主任务 (FreeRTOS 线程)
* @param argument 线程传入参数
*/
void CmdTask(void *argument);
#endif // TRONONEH7_SCAFFOLD_DEV_CMD_H

View File

@@ -0,0 +1,8 @@
//
// Created by tuxmonkey on 2026/3/7.
//
#ifndef TRONONEH7_SCAFFOLD_DEV_DEF_H
#define TRONONEH7_SCAFFOLD_DEV_DEF_H
#endif // TRONONEH7_SCAFFOLD_DEV_DEF_H

View File

@@ -0,0 +1,3 @@
//
// Created by tuxmonkey on 2026/3/7.
//

View File

@@ -0,0 +1,166 @@
#include "halfsteering_cmd.h"
#include "halfsteering_def.h"
#include "chassis_half_steer.h" // 包含底盘应用层结构体
#include "pid.h"
#include "rc.h"
#include "user_lib.h"
#include <string.h>
#include <math.h>
// --- 外部依赖 ---
extern Chassis_HalfSteer_t chassis_half_steer; // 假设在app层定义好的底盘实例
// --- 本地任务数据 ---
static HalfSteer_Task_Data_t task_data;
static const RC_ctrl_t *local_rc_ctrl;
// ================= PID 参数定义与实例化 =================
static PIDInstance chassis_follow_pid;
// 按你的格式直接在这里定义并初始化配置结构体
static PID_Init_Config_s chassis_follow_pid_conf = {
.Kp = 6.0f,
.Ki = 0.0f,
.Kd = 0.495f,
// .DeadBand = 0.5f,
//.CoefA = 0.2f,
//.CoefB = 0.3f,
//.Improve = PID_Trapezoid_Intergral | PID_DerivativeFilter | PID_Derivative_On_Measurement | PID_Integral_Limit,
//.IntegralLimit = 50.0f,
.MaxOut = 45.0f,
//.Derivative_LPF_RC = 0.01f,
};
// --- 私有函数声明 ---
static void ModeSelection(void);
static void RemoteControlSet(Chassis_Ctrl_Cmd_s *cmd);
/**
* @brief 任务初始化
*/
void RobotCMDInit(void)
{
// 1. 获取遥控器指针
local_rc_ctrl = RC_Get_RC_Pointer(); // 请替换为你实际获取遥控器数据的接口
// 2. 初始化底盘跟随 PID
PIDInit(&chassis_follow_pid, &chassis_follow_pid_conf);
// 3. 任务数据清零
memset(&task_data, 0, sizeof(HalfSteer_Task_Data_t));
task_data.current_mode = MODE_RELAX;
task_data.init_done = 1;
}
/**
* @brief 任务核心循环 (放置于 RTOS Task 中)
*/
void CmdTask(void)
{
if (!task_data.init_done) return;
Chassis_Ctrl_Cmd_s chassis_cmd_send;
memset(&chassis_cmd_send, 0, sizeof(Chassis_Ctrl_Cmd_s));
// 1. 状态机选择
ModeSelection();
// 2. 根据状态设定底层指令
RemoteControlSet(&chassis_cmd_send);
// 3. 将计算完成的指令发送给底盘应用层
Chassis_HalfSteer_Update(&chassis_half_steer, &chassis_cmd_send);
}
/**
* @brief 状态机模式选择 (复刻你的多拨杆判断逻辑)
*/
static void ModeSelection(void)
{
// 左侧[中], 右侧[下] -> 视觉模式
if (switch_is_mid(local_rc_ctrl->rc.s[1]) && switch_is_down(local_rc_ctrl->rc.s[0]))
{
task_data.current_mode = MODE_AUTO_VISION;
}
// 左侧[中], 右侧[中] -> 遥控器不跟随
else if (switch_is_mid(local_rc_ctrl->rc.switch[1])
&&
switch_is_mid(local_rc_ctrl->rc.switch[0])
) {
task_data.current_mode = MODE_NO_FOLLOW;
}
// 左侧[下], 右侧[下] -> 底盘小陀螺
else
if (switch_is_down(local_rc_ctrl->rc.s[1]) && switch_is_down(local_rc_ctrl->rc.s[0]))
{
task_data.current_mode = MODE_REMOTE_SPIN;
}
// 左侧[下], 右侧[中] -> 遥控器不跟随 (你原代码中的独立判断)
else if (switch_is_down(local_rc_ctrl->rc.s[1]) && switch_is_mid(local_rc_ctrl->rc.s[0]))
{
task_data.current_mode = MODE_NO_FOLLOW;
}
// 左侧[下] (单边条件兜底) -> 默认跟随模式
else if (switch_is_down(local_rc_ctrl->rc.s[1]))
{
task_data.current_mode = MODE_REMOTE_FOLLOW;
}
else
{
task_data.current_mode = MODE_RELAX;
}
}
/**
* @brief 遥控器映射与 PID 计算
*/
static void RemoteControlSet(Chassis_Ctrl_Cmd_s *cmd)
{
if (task_data.current_mode == MODE_RELAX)
{
cmd->chassis_mode = CHASSIS_ZERO_FORCE;
return;
}
// --- 1. 底盘基础平移 (水平和竖直方向) ---
cmd->vx = RC_CHASSIS_SPEED_SCALE * ((float) local_rc_ctrl->rc.ch[1] / 660.0f); // 摇杆前进
cmd->vy = RC_CHASSIS_SPEED_SCALE * ((float) local_rc_ctrl->rc.ch[0] / 660.0f); // 摇杆平移
// 摇杆死区处理
if (fabsf(local_rc_ctrl->rc.ch[1]) < RC_DEADBAND) cmd->vx = 0;
if (fabsf(local_rc_ctrl->rc.ch[0]) < RC_DEADBAND) cmd->vy = 0;
// --- 2. 旋转量与模式映射 ---
switch (task_data.current_mode)
{
case MODE_REMOTE_FOLLOW:
cmd->chassis_mode = CHASSIS_FOLLOW_GIMBAL_YAW;
// 注意:此处需要你获取到底盘和云台的实际偏差角 (例如从 Gimbal 结构体或电机 feedback 中拿)
// 假设获取到的夹角叫 angle_error并已转换到 [-180, 180] 之间
float angle_error = 0.0f; /* 替换为获取真实误差的代码 */
// 使用在文件头部实例化的 chassis_follow_pid 计算 wz 输出
cmd->wz = PIDCalculate(&chassis_follow_pid, angle_error, 0.0f) / 100.0f;
break;
case MODE_REMOTE_SPIN:
cmd->chassis_mode = CHASSIS_ROTATE;
cmd->wz = RC_ROTATE_SPEED_SCALE;
break;
case MODE_AUTO_VISION:
// 在你给的代码中24赛季检录视觉模式时让底盘转小陀螺
cmd->chassis_mode = CHASSIS_ROTATE; // CHASSIS_RE_ROTATE 对应底层小陀螺逻辑
cmd->wz = RC_ROTATE_SPEED_SCALE;
break;
case MODE_NO_FOLLOW:
cmd->chassis_mode = CHASSIS_NO_FOLLOW;
cmd->wz = 0.0f; // 如果需要拨杆控制旋转,可以在这里加入摇杆映射
break;
default:
cmd->chassis_mode = CHASSIS_ZERO_FORCE;
break;
}
}

View File

@@ -0,0 +1,14 @@
#ifndef HALFSTEERING_CMD_H
#define HALFSTEERING_CMD_H
/**
* @brief 机器人核心控制任务初始化,会被RobotInit()调用
*/
void RobotCMDInit(void);
/**
* @brief 机器人核心控制任务,200Hz频率运行(必须高于视觉发送频率)
*/
void CmdTask(void);
#endif // HALFSTEERING_CMD_H

View File

@@ -0,0 +1,30 @@
#ifndef HALF_STEER_DEF_H
#define HALF_STEER_DEF_H
#include "stdint.h"
// ================= 遥控器映射系数 =================
#define RC_CHASSIS_SPEED_SCALE 80.0f // 摇杆转平移速度比例
#define RC_ROTATE_SPEED_SCALE 1.0f // 小陀螺模式固定自旋速度
#define RC_DEADBAND 10 // 摇杆死区
// ================= 机器人状态枚举 =================
typedef enum
{
MODE_RELAX = 0, // 急停/掉线模式
MODE_REMOTE_FOLLOW, // 纯遥控器:底盘跟随云台
MODE_REMOTE_SPIN, // 纯遥控器:底盘小陀螺
MODE_AUTO_VISION, // 视觉辅助模式
MODE_NO_FOLLOW, // 云台底盘分离 (不跟随)
} Robot_State_e;
// ================= 内部控制对象结构体 =================
typedef struct
{
Robot_State_e current_mode; // 当前状态机模式
uint8_t init_done; // 初始化完成标志
// 如果有视觉数据或云台数据需要跨函数传递,也可以加在这里
} HalfSteer_Task_Data_t;
#endif // HALF_STEER_DEF_H

View File

@@ -0,0 +1,3 @@
//
// Created by esqwt on 2026/3/3.
//

View File

@@ -3,25 +3,26 @@
OpenDocument="main.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Core/Src/main.c", Line=0
OpenToolbar="Debug", Floating=0, x=0, y=0
OpenWindow="Registers 1", DockArea=BOTTOM, x=5, y=0, w=492, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, FilteredItems=[], RefreshRate=1
OpenWindow="Source Files", DockArea=LEFT, x=0, y=0, w=539, h=202, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Source Files", DockArea=LEFT, x=0, y=0, w=539, h=203, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Disassembly", DockArea=BOTTOM, x=0, y=0, w=453, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=539, h=293, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=254
OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=539, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=254
OpenWindow="Memory 1", DockArea=BOTTOM, x=4, y=0, w=438, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, EditorAddress=0x200003D8
OpenWindow="Global Data", DockArea=RIGHT, x=0, y=1, w=561, h=330, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=2, w=539, h=146, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Global Data", DockArea=RIGHT, x=0, y=1, w=561, h=329, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=2, w=539, h=153, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Functions", DockArea=BOTTOM, x=3, y=0, w=411, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Data Sampling", DockArea=BOTTOM, x=1, y=0, w=355, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VisibleTab=0, UniformSampleSpacing=0
OpenWindow="Timeline", DockArea=RIGHT, x=0, y=0, w=561, h=312, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=1, DataPaneShown=1, PowerPaneShown=1, CodePaneShown=1, PinCursor="Cursor Movable", TimePerDiv="1 ns / Div", TimeStampFormat="Time", DataGraphDrawAsPoints=0, DataGraphLegendShown=1, DataGraphUniformSampleSpacing=0, DataGraphLegendPosition="355;0", DataGraphShowNamesAtCursor=0, PowerGraphDrawAsPoints=0, PowerGraphLegendShown=1, PowerGraphAvgFilterTime=Off, PowerGraphAvgFilterLen=Off, PowerGraphUniformSampleSpacing=0, PowerGraphLegendPosition="360;-67", CodeGraphLegendShown=1, CodeGraphLegendPosition="369;0"
OpenWindow="Timeline", DockArea=RIGHT, x=0, y=0, w=561, h=313, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=1, DataPaneShown=1, PowerPaneShown=1, CodePaneShown=1, PinCursor="Cursor Movable", TimePerDiv="1 ns / Div", TimeStampFormat="Time", DataGraphDrawAsPoints=0, DataGraphLegendShown=1, DataGraphUniformSampleSpacing=0, DataGraphLegendPosition="355;-67", DataGraphShowNamesAtCursor=0, PowerGraphDrawAsPoints=0, PowerGraphLegendShown=1, PowerGraphAvgFilterTime=Off, PowerGraphAvgFilterLen=Off, PowerGraphUniformSampleSpacing=0, PowerGraphLegendPosition="360;-67", CodeGraphLegendShown=1, CodeGraphLegendPosition="309;0"
OpenWindow="Console", DockArea=BOTTOM, x=2, y=0, w=406, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
SmartViewPlugin="", Page="", Toolbar="Hidden", Window="SmartView 1"
TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[100;100;100;100;100;364]
TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[100;100;100;100;100;100]
TableHeader="Vector Catches", SortCol="", SortOrder="ASCENDING", VisibleCols=["";"Vector Catch";"Description"], ColWidths=[50;300;500]
TableHeader="Break & Tracepoints", SortCol="None", SortOrder="ASCENDING", VisibleCols=["";"Type";"Location";"Extras"], ColWidths=[100;100;100;239]
TableHeader="Functions", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Address";"Size";"#Insts";"Source"], ColWidths=[1435;100;100;100;231]
TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[225;100;100;100;875]
TableHeader="Functions", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Address";"Size";"#Insts";"Source"], ColWidths=[1435;100;100;100;100]
TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[225;100;100;100;100]
TableHeader="Data Sampling Table", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Index";"Time"], ColWidths=[100;100]
TableHeader="Data Sampling Setup", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Type";"Value";"Min";"Max";"Average";"# Changes";"Min. Change";"Max. Change"], ColWidths=[126;100;100;100;100;100;100;100;100]
TableHeader="Power Sampling", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Index";"Time";"Ch 0"], ColWidths=[100;100;100]
TableHeader="Registers 1", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Description"], ColWidths=[100;105;287]
TableHeader="Registers 1", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Description"], ColWidths=[100;105;271]
TableHeader="Watched Data 1", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Value";"Location";"Refresh"], ColWidths=[170;100;100;169]
TableHeader="RegisterSelectionDialog", SortCol="None", SortOrder="ASCENDING", VisibleCols=[], ColWidths=[]
TableHeader="RegisterSelectionDialog", SortCol="None", SortOrder="ASCENDING", VisibleCols=[], ColWidths=[]
TableHeader="TargetExceptionDialog", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Address";"Description"], ColWidths=[26;26;26;26]

363
ozonedeb/linuxdb3.jdebug Normal file
View File

@@ -0,0 +1,363 @@
/*********************************************************************
* (c) SEGGER Microcontroller GmbH *
* The Embedded Experts *
* www.segger.com *
**********************************************************************
File : /home/tuxmonkey/CLionProjects/tronone-h7-scaffold/ozonedeb/linuxdb3.jdebug
Created : 8 Mar 2026 01:05
Ozone Version : V3.38d
*/
/*********************************************************************
*
* OnProjectLoad
*
* Function description
* Project load routine. Required.
*
**********************************************************************
*/
void OnProjectLoad (void) {
//
// Dialog-generated settings
//
Project.AddPathSubstitute ("/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/ozonedeb", "$(ProjectDir)");
Project.AddPathSubstitute ("/home/tuxmonkey/clionprojects/tronone-h7-scaffold/ozonedeb", "$(ProjectDir)");
Project.SetDevice ("STM32H723VG");
Project.SetHostIF ("USB", "63728936");
Project.SetTargetIF ("SWD");
Project.SetTIFSpeed ("4 MHz");
Project.AddSvdFile ("$(InstallDir)/Config/CPU/Cortex-M7F.svd");
Project.AddSvdFile ("/opt/st/stm32cubeclt_1.19.0/STMicroelectronics_CMSIS_SVD/STM32H723.svd");
//
// User settings
//
File.Open ("/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/cmake-build-debug/TronOneH7_Scaffold.elf");
Project.SetOSPlugin ("FreeRTOSPlugin_CM4");
}
/*********************************************************************
*
* OnStartupComplete
*
* Function description
* Called when program execution has reached/passed
* the startup completion point. Optional.
*
**********************************************************************
*/
//void OnStartupComplete (void) {
//}
/*********************************************************************
*
* TargetReset
*
* Function description
* Replaces the default target device reset routine. Optional.
*
* Notes
* This example demonstrates the usage when
* debugging an application in RAM on a Cortex-M target device.
*
**********************************************************************
*/
//void TargetReset (void) {
//
// unsigned int SP;
// unsigned int PC;
// unsigned int VectorTableAddr;
//
// VectorTableAddr = Elf.GetBaseAddr();
// //
// // Set up initial stack pointer
// //
// if (VectorTableAddr != 0xFFFFFFFF) {
// SP = Target.ReadU32(VectorTableAddr);
// Target.SetReg("SP", SP);
// }
// //
// // Set up entry point PC
// //
// PC = Elf.GetEntryPointPC();
//
// if (PC != 0xFFFFFFFF) {
// Target.SetReg("PC", PC);
// } else if (VectorTableAddr != 0xFFFFFFFF) {
// PC = Target.ReadU32(VectorTableAddr + 4);
// Target.SetReg("PC", PC);
// } else {
// Util.Error("Project file error: failed to set entry point PC", 1);
// }
//}
/*********************************************************************
*
* BeforeTargetReset
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void BeforeTargetReset (void) {
//}
/*********************************************************************
*
* AfterTargetReset
*
* Function description
* Event handler routine. Optional.
* The default implementation initializes SP and PC to reset values.
**
**********************************************************************
*/
void AfterTargetReset (void) {
_SetupTarget();
}
/*********************************************************************
*
* DebugStart
*
* Function description
* Replaces the default debug session startup routine. Optional.
*
**********************************************************************
*/
//void DebugStart (void) {
//}
/*********************************************************************
*
* TargetConnect
*
* Function description
* Replaces the default target IF connection routine. Optional.
*
**********************************************************************
*/
//void TargetConnect (void) {
//}
/*********************************************************************
*
* BeforeTargetConnect
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void BeforeTargetConnect (void) {
//}
/*********************************************************************
*
* AfterTargetConnect
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void AfterTargetConnect (void) {
//}
/*********************************************************************
*
* TargetDownload
*
* Function description
* Replaces the default program download routine. Optional.
*
**********************************************************************
*/
//void TargetDownload (void) {
//}
/*********************************************************************
*
* BeforeTargetDownload
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void BeforeTargetDownload (void) {
//}
/*********************************************************************
*
* AfterTargetDownload
*
* Function description
* Event handler routine. Optional.
* The default implementation initializes SP and PC to reset values.
*
**********************************************************************
*/
void AfterTargetDownload (void) {
_SetupTarget();
}
/*********************************************************************
*
* BeforeTargetDisconnect
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void BeforeTargetDisconnect (void) {
//}
/*********************************************************************
*
* AfterTargetDisconnect
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void AfterTargetDisconnect (void) {
//}
/*********************************************************************
*
* AfterTargetHalt
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void AfterTargetHalt (void) {
//}
/*********************************************************************
*
* BeforeTargetResume
*
* Function description
* Event handler routine. Optional.
*
**********************************************************************
*/
//void BeforeTargetResume (void) {
//}
/*********************************************************************
*
* OnSnapshotLoad
*
* Function description
* Called upon loading a snapshot. Optional.
*
* Additional information
* This function is used to restore the target state in cases
* where values cannot simply be written to the target.
* Typical use: GPIO clock needs to be enabled, before
* GPIO is configured.
*
**********************************************************************
*/
//void OnSnapshotLoad (void) {
//}
/*********************************************************************
*
* OnSnapshotSave
*
* Function description
* Called upon saving a snapshot. Optional.
*
* Additional information
* This function is usually used to save values of the target
* state which can either not be trivially read,
* or need to be restored in a specific way or order.
* Typically use: Memory Mapped Registers,
* such as PLL and GPIO configuration.
*
**********************************************************************
*/
//void OnSnapshotSave (void) {
//}
/*********************************************************************
*
* OnError
*
* Function description
* Called when an error ocurred. Optional.
*
**********************************************************************
*/
//void OnError (void) {
//}
/*********************************************************************
*
* AfterProjectLoad
*
* Function description
* After Project load routine. Optional.
*
**********************************************************************
*/
//void AfterProjectLoad (void) {
//}
/*********************************************************************
*
* OnDebugStartBreakSymbolReached
*
* Function description
* Called when program execution has reached/passed
* the symbol to be breaked at during debug start. Optional.
*
**********************************************************************
*/
//void OnDebugStartBreakSymReached (void) {
//}
/*********************************************************************
*
* _SetupTarget
*
* Function description
* Setup the target.
* Called by AfterTargetReset() and AfterTargetDownload().
*
* Auto-generated function. May be overridden by Ozone.
*
**********************************************************************
*/
void _SetupTarget(void) {
unsigned int SP;
unsigned int PC;
unsigned int VectorTableAddr;
VectorTableAddr = Elf.GetBaseAddr();
//
// Set up initial stack pointer
//
SP = Target.ReadU32(VectorTableAddr);
if (SP != 0xFFFFFFFF) {
Target.SetReg("SP", SP);
}
//
// Set up entry point PC
//
PC = Elf.GetEntryPointPC();
if (PC != 0xFFFFFFFF) {
Target.SetReg("PC", PC);
} else {
Util.Error("Project script error: failed to set up entry point PC", 1);
}
}

View File

@@ -0,0 +1,48 @@
Breakpoint=/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/module/periph/power_meters/xiditech/xidipwmeter.c:26, State=BP_STATE_DISABLED
Breakpoint=/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/module/periph/power_meters/xiditech/xidipwmeter.c:64, State=BP_STATE_ON
Breakpoint=/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/module/periph/power_meters/xiditech/xidipwmeter.c:91, State=BP_STATE_DISABLED
OpenDocument="stm32h7xx_it.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Core/Src/stm32h7xx_it.c", Line=107
OpenDocument="portmacro.h", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h", Line=188
OpenDocument="port.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c", Line=211
OpenDocument="daemon.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/module/software/daemon/daemon.c", Line=46
OpenDocument="xidipwmeter.h", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/module/periph/power_meters/xiditech/xidipwmeter.h", Line=35
OpenDocument="tasks.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Middlewares/Third_Party/FreeRTOS/Source/tasks.c", Line=2298
OpenDocument="main.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Core/Src/main.c", Line=74
OpenDocument="delayticks.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/bsp/delayticks/delayticks.c", Line=12
OpenDocument="stm32h7xx_hal_fdcan.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_fdcan.c", Line=2014
OpenDocument="bsp_fdcan.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/bsp/fdcan/bsp_fdcan.c", Line=112
OpenDocument="xidipwmeter.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/module/periph/power_meters/xiditech/xidipwmeter.c", Line=57
OpenDocument="stm32h7xx_hal_spi.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_spi.c", Line=1553
OpenDocument="dev_cmd.c", FilePath="/home/tuxmonkey/CLionProjects/tronone-h7-scaffold/User_Code/user_task/dev_tasks/dev_cmd.c", Line=15
OpenToolbar="Debug", Floating=0, x=0, y=0
OpenWindow="Registers 1", DockArea=BOTTOM, x=5, y=0, w=497, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, FilteredItems=[], RefreshRate=1
OpenWindow="Source Files", DockArea=LEFT, x=0, y=0, w=539, h=198, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Disassembly", DockArea=BOTTOM, x=0, y=0, w=432, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=539, h=239, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=254
OpenWindow="Memory 1", DockArea=BOTTOM, x=4, y=0, w=442, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, EditorAddress=0x200003D8
OpenWindow="Global Data", DockArea=RIGHT, x=0, y=2, w=561, h=173, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=2, w=539, h=204, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Functions", DockArea=BOTTOM, x=3, y=0, w=415, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Data Sampling", DockArea=BOTTOM, x=1, y=0, w=359, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VisibleTab=0, UniformSampleSpacing=0
OpenWindow="Timeline", DockArea=RIGHT, x=0, y=1, w=561, h=293, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=1, DataPaneShown=1, PowerPaneShown=0, CodePaneShown=0, PinCursor="Cursor Movable", TimePerDiv="1 ns / Div", TimeStampFormat="Time", DataGraphDrawAsPoints=0, DataGraphLegendShown=1, DataGraphUniformSampleSpacing=0, DataGraphLegendPosition="355;0", DataGraphShowNamesAtCursor=0, PowerGraphDrawAsPoints=0, PowerGraphLegendShown=0, PowerGraphAvgFilterTime=Off, PowerGraphAvgFilterLen=Off, PowerGraphUniformSampleSpacing=0, PowerGraphLegendPosition="360;-67", CodeGraphLegendShown=0, CodeGraphLegendPosition="369;0"
OpenWindow="Console", DockArea=BOTTOM, x=2, y=0, w=410, h=285, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="FreeRTOS", DockArea=RIGHT, x=0, y=0, w=561, h=175, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, Showing="Task List"
SmartViewPlugin="", Page="", Toolbar="Hidden", Window="SmartView 1"
TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Access";"Scope"], ColWidths=[100;100;100;100;100;0;100]
TableHeader="Vector Catches", SortCol="", SortOrder="ASCENDING", VisibleCols=["";"Vector Catch";"Description"], ColWidths=[50;300;500]
TableHeader="Break & Tracepoints", SortCol="None", SortOrder="ASCENDING", VisibleCols=["";"Type";"Location";"Extras"], ColWidths=[100;100;100;553]
TableHeader="Functions", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Address";"Size";"#Insts";"Class";"Source"], ColWidths=[1435;100;100;100;27;245]
TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[225;100;100;100;931]
TableHeader="Data Sampling Table", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Index";"Time"], ColWidths=[100;100]
TableHeader="Data Sampling Setup", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Type";"Value";"Min";"Max";"Average";"# Changes";"Min. Change";"Max. Change"], ColWidths=[126;100;100;100;100;100;100;100;100]
TableHeader="Power Sampling", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Index";"Time";"Ch 0"], ColWidths=[100;100;100]
TableHeader="Task List", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Name";"Run Count";"Priority";"Status";"Timeout";"Stack Info";"ID";"Mutex Count";"Notified Value";"Notify State"], ColWidths=[110;110;110;110;110;110;110;110;110;110]
TableHeader="Registers 1", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Description"], ColWidths=[100;105;276]
TableHeader="Watched Data 1", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Value";"Location";"Refresh";"Access"], ColWidths=[170;100;100;169;100]
TableHeader="RegisterSelectionDialog", SortCol="None", SortOrder="ASCENDING", VisibleCols=[], ColWidths=[]
TableHeader="TargetExceptionDialog", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Address";"Description"], ColWidths=[200;100;100;358]
WatchedExpression="Daemon_Init_Config_s", RefreshRate=2, Window=Watched Data 1
WatchedExpression="power_meter_instance", Window=Watched Data 1
WatchedExpression="powermeter_msg", Window=Watched Data 1

View File

@@ -22,12 +22,12 @@ void OnProjectLoad (void) {
//
// Dialog-generated settings
//
Project.AddPathSubstitute ("D:/RM/Elec Control/TronOneH7_Scaffold/ozonedeb", "$(ProjectDir)");
Project.AddPathSubstitute ("d:/rm/elec control/trononeh7_scaffold/ozonedeb", "$(ProjectDir)");
Project.SetDevice ("STM32H723VG");
Project.SetHostIF ("USB", "602717886");
Project.SetHostIF ("USB", "63728936");
Project.SetTargetIF ("SWD");
Project.SetTIFSpeed ("4 MHz");
Project.AddPathSubstitute ("D:/RM/Elec Control/TronOneH7_Scaffold/ozonedeb", "$(ProjectDir)");
Project.AddPathSubstitute ("d:/rm/elec control/trononeh7_scaffold/ozonedeb", "$(ProjectDir)");
Project.AddSvdFile ("$(InstallDir)/Config/CPU/Cortex-M7F.svd");
Project.AddSvdFile ("D:/ST/STM32CubeCLT_1.19.0/STMicroelectronics_CMSIS_SVD/STM32H723.svd");
//

View File

@@ -1,52 +1,35 @@
Breakpoint=D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/module/periph/buzzer/buzzer.cpp:336:1, State=BP_STATE_DISABLED
GraphedExpression="(QEKF_INS).Roll", Color=#a00909
OpenDocument="ws2812.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/module/periph/ws2812/ws2812.c", Line=0
OpenDocument="buzzer.cpp", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/module/periph/buzzer/buzzer.cpp", Line=319
OpenDocument="rc.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/module/periph/remote_control/rc.c", Line=0
OpenDocument="delayticks.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/bsp/delayticks/delayticks.c", Line=0
OpenDocument="bsp_dwt.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/bsp/dwt/bsp_dwt.c", Line=93
OpenDocument="main.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Core/Src/main.c", Line=69
OpenDocument="robot.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/application/robot.c", Line=33
OpenDocument="tasks.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Middlewares/Third_Party/FreeRTOS/Source/tasks.c", Line=2304
OpenDocument="stm32h7xx_hal.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal.c", Line=404
OpenDocument="list.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Middlewares/Third_Party/FreeRTOS/Source/list.c", Line=144
OpenDocument="cmsis_gcc.h", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Drivers/CMSIS/Include/cmsis_gcc.h", Line=264
OpenDocument="cmsis_os2.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.c", Line=872
OpenDocument="stm32h7xx_it.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Core/Src/stm32h7xx_it.c", Line=99
OpenDocument="stm32h7xx_hal_spi.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_spi.c", Line=3887
OpenDocument="ins_task.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/User_Code/module/periph/imu/ins_task.c", Line=0
GraphedExpression="(rc_ctrl[0]).ch3", DisplayFormat=DISPLAY_FORMAT_DEC, Color=#a00909
OpenDocument="main.c", FilePath="D:/RM/Elec Control/TronOneH7_Scaffold/Core/Src/main.c", Line=64
OpenToolbar="Debug", Floating=0, x=0, y=0
OpenToolbar="Breakpoints", Floating=0, x=1, y=0
OpenWindow="Call Stack", DockArea=RIGHT, x=0, y=0, w=681, h=254, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Call Stack", DockArea=RIGHT, x=0, y=0, w=681, h=226, TabPos=0, TopOfStack=1, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Registers 1", DockArea=BOTTOM, x=1, y=0, w=361, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, FilteredItems=[], RefreshRate=1
OpenWindow="Source Files", DockArea=LEFT, x=0, y=0, w=551, h=273, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Disassembly", DockArea=BOTTOM, x=2, y=0, w=629, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=551, h=282, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=254
OpenWindow="Memory 1", DockArea=BOTTOM, x=3, y=0, w=465, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, EditorAddress=0x20005E30
OpenWindow="Global Data", DockArea=RIGHT, x=0, y=3, w=681, h=310, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=2, w=551, h=282, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Functions", DockArea=LEFT, x=0, y=3, w=551, h=235, TabPos=1, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Call Graph", DockArea=LEFT, x=0, y=3, w=551, h=235, TabPos=2, TopOfStack=1, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Source Files", DockArea=LEFT, x=0, y=0, w=663, h=189, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Disassembly", DockArea=BOTTOM, x=2, y=0, w=632, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=663, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=254
OpenWindow="Memory 1", DockArea=BOTTOM, x=3, y=0, w=462, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, EditorAddress=0x2000B98C
OpenWindow="Global Data", DockArea=RIGHT, x=0, y=2, w=681, h=260, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=2, w=663, h=201, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Functions", DockArea=LEFT, x=0, y=3, w=663, h=168, TabPos=1, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Call Graph", DockArea=LEFT, x=0, y=3, w=663, h=168, TabPos=2, TopOfStack=1, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Data Sampling", DockArea=BOTTOM, x=0, y=0, w=1102, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VisibleTab=0, UniformSampleSpacing=0
OpenWindow="Timeline", DockArea=RIGHT, x=0, y=2, w=681, h=303, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=1, DataPaneShown=1, PowerPaneShown=0, CodePaneShown=0, PinCursor="Cursor Movable", TimePerDiv="1 s / Div", TimeStampFormat="Time", DataGraphDrawAsPoints=0, DataGraphLegendShown=1, DataGraphUniformSampleSpacing=0, DataGraphLegendPosition="435;0", PowerGraphDrawAsPoints=0, PowerGraphLegendShown=0, PowerGraphAvgFilterTime=Off, PowerGraphAvgFilterLen=Off, PowerGraphUniformSampleSpacing=0, PowerGraphLegendPosition="459;-65", CodeGraphLegendShown=0, CodeGraphLegendPosition="475;0"
OpenWindow="Console", DockArea=LEFT, x=0, y=3, w=551, h=235, TabPos=0, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="FreeRTOS", DockArea=RIGHT, x=0, y=1, w=681, h=225, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, Showing="Task List"
TableHeader="Functions", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Address";"Size";"#Insts";"Source"], ColWidths=[1164;100;100;100;100]
TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[222;130;100;54;95;486]
TableHeader="Vector Catches", SortCol="None", SortOrder="ASCENDING", VisibleCols=["";"Vector Catch";"Description"], ColWidths=[50;300;500]
TableHeader="Break & Tracepoints", SortCol="None", SortOrder="ASCENDING", VisibleCols=["";"Type";"Location";"Extras"], ColWidths=[100;100;142;209]
TableHeader="Call Stack", SortCol="Function", SortOrder="ASCENDING", VisibleCols=["Function";"Stack Frame";"Source";"PC";"Return Address";"Stack Used"], ColWidths=[110;126;190;100;190;100]
TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[229;100;100;100;1046]
TableHeader="Data Sampling Table", SortCol="Index", SortOrder="ASCENDING", VisibleCols=["Index";"Time";" (QEKF_INS).Roll"], ColWidths=[100;100;100]
TableHeader="Data Sampling Setup", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Type";"Value";"Min";"Max";"Average";"# Changes";"Min. Change";"Max. Change"], ColWidths=[320;100;100;102;100;102;102;118;118]
TableHeader="Power Sampling", SortCol="Index", SortOrder="ASCENDING", VisibleCols=["Index";"Time";"Ch 0"], ColWidths=[100;100;100]
OpenWindow="Timeline", DockArea=RIGHT, x=0, y=1, w=681, h=254, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=1, DataPaneShown=1, PowerPaneShown=0, CodePaneShown=0, PinCursor="Cursor Movable", TimePerDiv="1 s / Div", TimeStampFormat="Time", DataGraphDrawAsPoints=0, DataGraphLegendShown=1, DataGraphUniformSampleSpacing=0, DataGraphLegendPosition="0;0", PowerGraphDrawAsPoints=0, PowerGraphLegendShown=0, PowerGraphAvgFilterTime=Off, PowerGraphAvgFilterLen=Off, PowerGraphUniformSampleSpacing=0, PowerGraphLegendPosition="484;-19", CodeGraphLegendShown=0, CodeGraphLegendPosition="498;0"
OpenWindow="Console", DockArea=LEFT, x=0, y=3, w=663, h=168, TabPos=0, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="FreeRTOS", DockArea=RIGHT, x=0, y=0, w=681, h=226, TabPos=1, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, Showing="Task List"
TableHeader="Call Graph", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Stack Total";"Stack Local";"Code Total";"Code Local";"Depth";"Called From"], ColWidths=[384;100;100;100;100;100;102]
TableHeader="Task List", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Run Count";"Priority";"Status";"Timeout";"Stack Info (Free / Size)";"ID";"Mutex Count";"Notified Value";"Notify State"], ColWidths=[110;110;110;110;110;110;110;110;110;110]
TableHeader="Registers 1", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Description"], ColWidths=[100;105;294]
TableHeader="Watched Data 1", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Value";"Location"], ColWidths=[171;101;279]
TableHeader="Functions", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Address";"Size";"#Insts";"Source"], ColWidths=[1164;100;100;100;100]
TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[222;130;100;54;95;427]
TableHeader="Vector Catches", SortCol="None", SortOrder="ASCENDING", VisibleCols=["";"Vector Catch";"Description"], ColWidths=[50;300;500]
TableHeader="Break & Tracepoints", SortCol="None", SortOrder="ASCENDING", VisibleCols=["";"Type";"Location";"Extras"], ColWidths=[100;100;142;321]
TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[229;100;100;100;917]
TableHeader="Data Sampling Table", SortCol="None", SortOrder="ASCENDING", VisibleCols=["Index";"Time";" (rc_ctrl[0]).ch3"], ColWidths=[100;100;100]
TableHeader="Data Sampling Setup", SortCol="Value", SortOrder="ASCENDING", VisibleCols=["Expression";"Type";"Value";"Min";"Max";"Average";"# Changes";"Min. Change";"Max. Change"], ColWidths=[320;100;100;100;100;100;100;102;102]
TableHeader="Power Sampling", SortCol="Index", SortOrder="ASCENDING", VisibleCols=["Index";"Time";"Ch 0"], ColWidths=[100;100;100]
TableHeader="Registers 1", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Description"], ColWidths=[100;105;259]
TableHeader="Watched Data 1", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Value";"Type";"Location";"Refresh"], ColWidths=[171;101;162;112;117]
TableHeader="RegisterSelectionDialog", SortCol="None", SortOrder="ASCENDING", VisibleCols=[], ColWidths=[]
TableHeader="Call Graph", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Stack Total";"Stack Local";"Code Total";"Code Local";"Depth";"Called From"], ColWidths=[384;100;100;100;100;100;118]
TableHeader="TargetExceptionDialog", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Address";"Description"], ColWidths=[27;27;27;38]
WatchedExpression="QEKF_INS", RefreshRate=2, Window=Watched Data 1
WatchedExpression="BMI088", RefreshRate=2, Window=Watched Data 1
WatchedExpression="RobotMode_t", Window=Watched Data 1
TableHeader="Call Stack", SortCol="Function", SortOrder="ASCENDING", VisibleCols=["Function";"Stack Frame";"Source";"PC";"Return Address";"Stack Used"], ColWidths=[110;126;190;100;190;100]
TableHeader="TargetExceptionDialog", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Address";"Description"], ColWidths=[27;27;27;32]
WatchedExpression="rc_ctrl", RefreshRate=1, Window=Watched Data 1

View File

@@ -2,26 +2,26 @@
GraphedExpression="(INS).Yaw", Color=#a00909
GraphedExpression="(INS).Pitch", Color=#09a01b
GraphedExpression="(INS).Roll", Color=#09a087
OpenDocument="delayticks.c", FilePath="C:/Users/esqwt/CLionProjects/tronone-h7-scaffold/User_Code/bsp/delayticks/delayticks.c", Line=14
OpenDocument="delayticks.c", FilePath="C:/Users/esqwt/CLionProjects/tronone-h7-scaffold/User_Code/bsp/delayticks/delayticks.c", Line=7
OpenDocument="ins_task.c", FilePath="C:/Users/esqwt/CLionProjects/tronone-h7-scaffold/User_Code/module/periph/imu/ins_task.c", Line=217
OpenDocument="tasks.c", FilePath="C:/Users/esqwt/CLionProjects/tronone-h7-scaffold/Middlewares/Third_Party/FreeRTOS/Source/tasks.c", Line=2298
OpenDocument="tasks.c", FilePath="C:/Users/esqwt/CLionProjects/tronone-h7-scaffold/Middlewares/Third_Party/FreeRTOS/Source/tasks.c", Line=2308
OpenDocument="main.c", FilePath="C:/Users/esqwt/CLionProjects/tronone-h7-scaffold/Core/Src/main.c", Line=74
OpenToolbar="Debug", Floating=0, x=0, y=0
OpenToolbar="Breakpoints", Floating=0, x=1, y=0
OpenWindow="Call Stack", DockArea=LEFT, x=0, y=3, w=551, h=159, TabPos=0, TopOfStack=1, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Call Stack", DockArea=LEFT, x=0, y=3, w=551, h=161, TabPos=0, TopOfStack=1, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Registers 1", DockArea=BOTTOM, x=1, y=0, w=271, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, FilteredItems=[], RefreshRate=1
OpenWindow="Source Files", DockArea=LEFT, x=0, y=0, w=551, h=187, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Disassembly", DockArea=BOTTOM, x=2, y=0, w=472, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Break & Tracepoints", DockArea=LEFT, x=0, y=1, w=551, h=194, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VectorCatchIndexMask=254
OpenWindow="Memory 1", DockArea=BOTTOM, x=3, y=0, w=348, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, EditorAddress=0x20005E30
OpenWindow="Global Data", DockArea=RIGHT, x=0, y=2, w=614, h=269, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=2, w=551, h=190, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Functions", DockArea=LEFT, x=0, y=3, w=551, h=159, TabPos=2, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Call Graph", DockArea=LEFT, x=0, y=3, w=551, h=159, TabPos=3, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Global Data", DockArea=RIGHT, x=0, y=2, w=614, h=267, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Watched Data 1", DockArea=LEFT, x=0, y=2, w=551, h=188, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Functions", DockArea=LEFT, x=0, y=3, w=551, h=161, TabPos=2, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Call Graph", DockArea=LEFT, x=0, y=3, w=551, h=161, TabPos=3, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="Data Sampling", DockArea=BOTTOM, x=0, y=0, w=826, h=181, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, VisibleTab=0, UniformSampleSpacing=0
OpenWindow="Timeline", DockArea=RIGHT, x=0, y=1, w=614, h=267, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=1, DataPaneShown=1, PowerPaneShown=0, CodePaneShown=0, PinCursor="Cursor Movable", TimePerDiv="2 s / Div", TimeStampFormat="Time", DataGraphDrawAsPoints=0, DataGraphLegendShown=1, DataGraphUniformSampleSpacing=0, DataGraphLegendPosition="368;0", PowerGraphDrawAsPoints=0, PowerGraphLegendShown=0, PowerGraphAvgFilterTime=Off, PowerGraphAvgFilterLen=Off, PowerGraphUniformSampleSpacing=0, PowerGraphLegendPosition="396;-65", CodeGraphLegendShown=0, CodeGraphLegendPosition="412;0"
OpenWindow="Console", DockArea=LEFT, x=0, y=3, w=551, h=159, TabPos=1, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="FreeRTOS", DockArea=RIGHT, x=0, y=0, w=614, h=215, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, Showing="Task List"
OpenWindow="Timeline", DockArea=RIGHT, x=0, y=1, w=614, h=263, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=1, DataPaneShown=1, PowerPaneShown=0, CodePaneShown=0, PinCursor="Cursor Movable", TimePerDiv="2 s / Div", TimeStampFormat="Time", DataGraphDrawAsPoints=0, DataGraphLegendShown=1, DataGraphUniformSampleSpacing=0, DataGraphLegendPosition="368;0", PowerGraphDrawAsPoints=0, PowerGraphLegendShown=0, PowerGraphAvgFilterTime=Off, PowerGraphAvgFilterLen=Off, PowerGraphUniformSampleSpacing=0, PowerGraphLegendPosition="396;-65", CodeGraphLegendShown=0, CodeGraphLegendPosition="412;0"
OpenWindow="Console", DockArea=LEFT, x=0, y=3, w=551, h=161, TabPos=1, TopOfStack=0, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0
OpenWindow="FreeRTOS", DockArea=RIGHT, x=0, y=0, w=614, h=221, FilterBarShown=0, TotalValueBarShown=0, ToolBarShown=0, Showing="Task List"
TableHeader="Call Graph", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Stack Total";"Stack Local";"Code Total";"Code Local";"Depth";"Called From"], ColWidths=[384;100;100;100;100;100;113]
TableHeader="Functions", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Address";"Size";"#Insts";"Source"], ColWidths=[1164;100;100;100;100]
TableHeader="Global Data", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Location";"Size";"Type";"Scope"], ColWidths=[222;130;100;54;95;486]
@@ -29,7 +29,7 @@ TableHeader="Vector Catches", SortCol="None", SortOrder="ASCENDING", VisibleCols
TableHeader="Break & Tracepoints", SortCol="None", SortOrder="ASCENDING", VisibleCols=["";"Type";"Location";"Extras"], ColWidths=[100;100;142;209]
TableHeader="Source Files", SortCol="File", SortOrder="ASCENDING", VisibleCols=["File";"Status";"Size";"#Insts";"Path"], ColWidths=[229;100;100;100;1046]
TableHeader="Data Sampling Table", SortCol="Index", SortOrder="ASCENDING", VisibleCols=["Index";"Time";" (INS).Yaw";" (INS).Pitch";" (INS).Roll"], ColWidths=[100;100;100;100;409]
TableHeader="Data Sampling Setup", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Type";"Value";"Min";"Max";"Average";"# Changes";"Min. Change";"Max. Change"], ColWidths=[105;100;102;110;102;102;100;118;113]
TableHeader="Data Sampling Setup", SortCol="Expression", SortOrder="ASCENDING", VisibleCols=["Expression";"Type";"Value";"Min";"Max";"Average";"# Changes";"Min. Change";"Max. Change"], ColWidths=[105;100;110;102;100;110;100;113;113]
TableHeader="Power Sampling", SortCol="Index", SortOrder="ASCENDING", VisibleCols=["Index";"Time";"Ch 0"], ColWidths=[100;100;100]
TableHeader="Task List", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Run Count";"Priority";"Status";"Timeout";"Stack Info (Free / Size)";"ID";"Mutex Count";"Notified Value";"Notify State"], ColWidths=[110;110;110;110;110;110;110;110;110;110]
TableHeader="Registers 1", SortCol="Name", SortOrder="ASCENDING", VisibleCols=["Name";"Value";"Description"], ColWidths=[100;105;294]

View File

@@ -122,6 +122,8 @@
华师佛山-VANGUARD [电控弹道解算例程](https://github.com/CodeAlanqian/SolveTrajectory)
达妙科技H7(MC02)开发板说明书及例程 [达妙H7(MC02)开发板官方资料](https://gitee.com/kit-miao/dm-mc02)
---
还要非常感谢愿意与我队电控一起交流,并无私提供帮助的这些队伍,他们是(以下排名不分先后):