50 Commits

Author SHA1 Message Date
TuxMonkey
0602520e4f feat: redesign PRTS main menu cards with large icons, black boot screen 2026-07-21 22:23:40 +08:00
TuxMonkey
dc57b5152e feat: refine remote states and PRTS LCD refresh
- add remote NOT_READY/READY/PROTECT state flow and matching LED/status display

- speed up TFT hardware SPI DMA flush path and larger LVGL partial refresh buffer

- reduce page/menu redraw artifacts and center the PRTS status title
2026-07-21 00:40:01 +08:00
TuxMonkey
8671f84139 feat: speed up PRTS LCD refresh 2026-07-20 23:34:13 +08:00
TuxMonkey
b2169513e1 tft lvgl test ok 2026-07-20 21:59:16 +08:00
TuxMonkey
8928b7c7db ioc changed hard spi tft 2026-07-20 18:09:56 +08:00
TuxMonkey
9ea9ddf310 ioc changed hard spi tft 2026-07-20 16:41:16 +08:00
TuxMonkey
f92585831a ioc changed soft spi tft 2026-07-20 16:29:06 +08:00
TuxMonkey
cda936d7e0 ioc changed but not tested 2026-07-20 15:39:14 +08:00
TuxMonkey
d13729b162 ioc changed but not tested 2026-07-20 15:39:06 +08:00
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
862 changed files with 451956 additions and 1513 deletions

View File

@@ -155,7 +155,7 @@ link_directories(
# A. 头文件管理:全自动模式,扫描 User_Code 下的所有头文件。
# ------------------------------------------------------------------------------
message(NOTICE ">> [Searching All Header] 正在全自动递归扫描 User_Code 下所有头文件路径...")
file(GLOB_RECURSE USER_HEADERS "${CMAKE_SOURCE_DIR}/User_Code/*.h" "${CMAKE_SOURCE_DIR}/User_Code/*.hpp")
file(GLOB_RECURSE USER_HEADERS CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/User_Code/*.h" "${CMAKE_SOURCE_DIR}/User_Code/*.hpp")
foreach (header ${USER_HEADERS})
get_filename_component(dir ${header} DIRECTORY) # 获取头文件所在目录
@@ -174,18 +174,17 @@ message(NOTICE ">> [INFO] 正在全自动递归扫描 User_Code/application下
# 1. 添加通用代码 (BSP & Module & Application 通用框架)
# 包含 bsp, module 以及 application 下的通用组件
file(GLOB_RECURSE COMMON_SOURCES
file(GLOB_RECURSE COMMON_SOURCES CONFIGURE_DEPENDS
"${PROJECT_SOURCE_DIR}/User_Code/bsp/*.c"
"${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"
)
@@ -270,7 +269,7 @@ endif()
if(ACTIVE_APP_DIR)
if(EXISTS "${ACTIVE_APP_DIR}")
# 添加该兵种目录下的所有源码
file(GLOB_RECURSE APP_SOURCES "${ACTIVE_APP_DIR}/*.c" "${ACTIVE_APP_DIR}/*.cpp")
file(GLOB_RECURSE APP_SOURCES CONFIGURE_DEPENDS "${ACTIVE_APP_DIR}/*.c" "${ACTIVE_APP_DIR}/*.cpp")
# --- 新增:统计文件数量 ---
list(LENGTH APP_SOURCES APP_SOURCES_COUNT)
@@ -295,4 +294,4 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
# RM_REFEREE
)
)

View File

@@ -167,6 +167,34 @@ standard names. */
/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
#include CMSIS_device_header
#define INCLUDE_xTaskGetIdleTaskHandle 1
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() \
do { \
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; \
DWT->CYCCNT = 0U; \
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; \
} while (0)
#define portGET_RUN_TIME_COUNTER_VALUE() DWT->CYCCNT
#ifndef traceTASK_SWITCHED_IN
#define traceTASK_SWITCHED_IN() \
do { \
extern void lv_freertos_task_switch_in(const char * name); \
lv_freertos_task_switch_in(pxCurrentTCB->pcTaskName); \
} while (0)
#endif
#ifndef traceTASK_SWITCHED_OUT
#define traceTASK_SWITCHED_OUT() \
do { \
extern void lv_freertos_task_switch_out(void); \
lv_freertos_task_switch_out(); \
} while (0)
#endif
/* USER CODE END Defines */
#endif /* FREERTOS_CONFIG_H */

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

@@ -36,15 +36,12 @@ extern SPI_HandleTypeDef hspi1;
extern SPI_HandleTypeDef hspi2;
extern SPI_HandleTypeDef hspi6;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_SPI1_Init(void);
void MX_SPI2_Init(void);
void MX_SPI6_Init(void);
/* USER CODE BEGIN Prototypes */

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

@@ -78,7 +78,7 @@ void MX_ADC1_Init(void)
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
sConfig.SamplingTime = ADC_SAMPLETIME_64CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
@@ -90,7 +90,9 @@ void MX_ADC1_Init(void)
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_19;
sConfig.Rank = ADC_REGULAR_RANK_2;
sConfig.SamplingTime = ADC_SAMPLETIME_64CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
@@ -171,10 +173,17 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
/* ADC1 clock enable */
__HAL_RCC_ADC12_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA5 ------> ADC1_INP19
PC4 ------> ADC1_INP4
*/
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
@@ -231,8 +240,11 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
__HAL_RCC_ADC12_CLK_DISABLE();
/**ADC1 GPIO Configuration
PA5 ------> ADC1_INP19
PC4 ------> ADC1_INP4
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5);
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_4);
/* ADC1 DMA DeInit */

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

@@ -28,12 +28,16 @@
#include "ws2812status.h"
#include "ins_task.h"
#include "buzzer.h"
#include "tft.h"
#include "prts.h"
// #include "remoteTask.h"
//
// #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 -----------------------------------------------------------*/
@@ -43,6 +47,8 @@
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PRTS_STATUS_UPDATE_PERIOD_MS 50U
#define PRTS_TASK_SLEEP_CAP_MS 1U
/* USER CODE END PD */
@@ -59,7 +65,7 @@
osThreadId_t BeginTaskHandle;
const osThreadAttr_t BeginTask_attributes = {
.name = "BeginTask",
.stack_size = 256 * 4,
.stack_size = 512 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for shoot */
@@ -131,35 +137,60 @@ 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);
static StackType_t prtsTaskStack[1024];
static StaticTask_t prtsTaskControlBlock;
const osThreadAttr_t prts_attributes = {
.name = "prts",
.cb_mem = &prtsTaskControlBlock,
.cb_size = sizeof(prtsTaskControlBlock),
.stack_mem = prtsTaskStack,
.stack_size = sizeof(prtsTaskStack),
.priority = osPriorityNormal
};
osThreadId_t prtsHandle;
void StartPRTSTask(void *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 +225,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 +279,18 @@ 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);
prtsHandle = osThreadNew(StartPRTSTask, NULL, &prts_attributes);
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
@@ -271,7 +308,7 @@ __weak void StartDefaultTask(void *argument)
/* Infinite loop */
for (;;)
{
osDelay(1);
osDelay(1000);
}
/* USER CODE END StartDefaultTask */
}
@@ -406,7 +443,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 +462,80 @@ __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() 整个删掉,防止干扰!
__attribute__((noreturn)) void StartPRTSTask(void *argument)
{
(void) argument;
bool prts_ok = PRTS_Init();
uint32_t refresh_ms = PRTS_STATUS_UPDATE_PERIOD_MS;
if (!prts_ok)
{
TFT_Init();
}
for (;;)
{
uint32_t delay_ms = PRTS_TASK_SLEEP_CAP_MS;
if (refresh_ms >= PRTS_STATUS_UPDATE_PERIOD_MS)
{
if (prts_ok)
{
PRTS_UpdateStatus(BMI088.Temperature, RobotMode);
}
else
{
TFT_ShowStatus(BMI088.Temperature, RobotMode);
}
refresh_ms = 0U;
}
if (prts_ok)
{
delay_ms = PRTS_Task();
if (delay_ms == 0U || delay_ms > PRTS_TASK_SLEEP_CAP_MS)
{
delay_ms = PRTS_TASK_SLEEP_CAP_MS;
}
}
else
{
prts_ok = PRTS_Init();
}
osDelay(delay_ms);
refresh_ms += delay_ms;
}
}
/* USER CODE END Application */

View File

@@ -38,6 +38,7 @@
* Output
* EVENT_OUT
* EXTI
PA7 ------> SPI6_MOSI
PA8 ------> RCC_MCO_1
PC10 ------> SPI3_SCK
PC11 ------> SPI3_MISO
@@ -56,10 +57,7 @@ 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);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15|GYRO_CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, Power2_Pin|Power1_Pin|Power_5V_EN_Pin|ACC_CS_Pin|GYRO_CS_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, power2_Pin|power1_Pin, GPIO_PIN_SET);
@@ -76,8 +74,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;
@@ -97,6 +95,20 @@ void MX_GPIO_Init(void)
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PA6 */
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PA7 */
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF8_SPI6;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : pump3_Pin pump4_Pin */
GPIO_InitStruct.Pin = pump3_Pin|pump4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

View File

@@ -35,6 +35,7 @@
#include "buzzer.h"
#include "ws2812status.h"
#include "robot.h"
#include "tft.h"
/* USER CODE END Includes */
@@ -133,10 +134,10 @@ int main(void)
MX_SPI1_Init();
MX_UART5_Init();
MX_TIM3_Init();
MX_SPI6_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
TFT_ShowBootScreen();
robotSelfCheck(); //自检LED
RobotInit();
systemstart_song(); //开机音乐

View File

@@ -26,7 +26,6 @@
SPI_HandleTypeDef hspi1;
SPI_HandleTypeDef hspi2;
SPI_HandleTypeDef hspi6;
DMA_HandleTypeDef hdma_spi1_rx;
DMA_HandleTypeDef hdma_spi1_tx;
DMA_HandleTypeDef hdma_spi2_rx;
@@ -46,11 +45,11 @@ void MX_SPI1_Init(void)
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
@@ -115,48 +114,6 @@ void MX_SPI2_Init(void)
/* USER CODE END SPI2_Init 2 */
}
/* SPI6 init function */
void MX_SPI6_Init(void)
{
/* USER CODE BEGIN SPI6_Init 0 */
/* USER CODE END SPI6_Init 0 */
/* USER CODE BEGIN SPI6_Init 1 */
/* USER CODE END SPI6_Init 1 */
hspi6.Instance = SPI6;
hspi6.Init.Mode = SPI_MODE_MASTER;
hspi6.Init.Direction = SPI_DIRECTION_2LINES_TXONLY;
hspi6.Init.DataSize = SPI_DATASIZE_8BIT;
hspi6.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi6.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi6.Init.NSS = SPI_NSS_SOFT;
hspi6.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi6.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi6.Init.TIMode = SPI_TIMODE_DISABLE;
hspi6.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi6.Init.CRCPolynomial = 0x0;
hspi6.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
hspi6.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
hspi6.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
hspi6.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
hspi6.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
hspi6.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
hspi6.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
hspi6.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
hspi6.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
hspi6.Init.IOSwap = SPI_IO_SWAP_DISABLE;
if (HAL_SPI_Init(&hspi6) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN SPI6_Init 2 */
/* USER CODE END SPI6_Init 2 */
}
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
@@ -192,14 +149,14 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
@@ -210,8 +167,8 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_rx.Init.Mode = DMA_NORMAL;
hdma_spi1_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
@@ -228,8 +185,8 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_tx.Init.Mode = DMA_NORMAL;
hdma_spi1_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
@@ -330,41 +287,6 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
/* USER CODE END SPI2_MspInit 1 */
}
else if(spiHandle->Instance==SPI6)
{
/* USER CODE BEGIN SPI6_MspInit 0 */
/* USER CODE END SPI6_MspInit 0 */
/** Initializes the peripherals clock
*/
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI6;
PeriphClkInitStruct.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSE;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
/* SPI6 clock enable */
__HAL_RCC_SPI6_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**SPI6 GPIO Configuration
PA5 ------> SPI6_SCK
PA6 ------> SPI6_MISO
PA7 ------> SPI6_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF8_SPI6;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN SPI6_MspInit 1 */
/* USER CODE END SPI6_MspInit 1 */
}
}
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
@@ -424,25 +346,6 @@ void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
/* USER CODE END SPI2_MspDeInit 1 */
}
else if(spiHandle->Instance==SPI6)
{
/* USER CODE BEGIN SPI6_MspDeInit 0 */
/* USER CODE END SPI6_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_SPI6_CLK_DISABLE();
/**SPI6 GPIO Configuration
PA5 ------> SPI6_SCK
PA6 ------> SPI6_MISO
PA7 ------> SPI6_MOSI
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
/* USER CODE BEGIN SPI6_MspDeInit 1 */
/* USER CODE END SPI6_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */

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

@@ -1,6 +1,6 @@
#MicroXplorer Configuration settings - do not modify
ADC1.Channel-0\#ChannelRegularConversion=ADC_CHANNEL_4
ADC1.Channel-1\#ChannelRegularConversion=ADC_CHANNEL_4
ADC1.Channel-1\#ChannelRegularConversion=ADC_CHANNEL_19
ADC1.ClockPrescaler=ADC_CLOCK_ASYNC_DIV64
ADC1.ContinuousConvMode=ENABLE
ADC1.ConversionDataManagement=ADC_CONVERSIONDATA_DMA_CIRCULAR
@@ -13,8 +13,8 @@ ADC1.OffsetSignedSaturation-0\#ChannelRegularConversion=DISABLE
ADC1.OffsetSignedSaturation-1\#ChannelRegularConversion=DISABLE
ADC1.Rank-0\#ChannelRegularConversion=1
ADC1.Rank-1\#ChannelRegularConversion=2
ADC1.SamplingTime-0\#ChannelRegularConversion=ADC_SAMPLETIME_1CYCLE_5
ADC1.SamplingTime-1\#ChannelRegularConversion=ADC_SAMPLETIME_1CYCLE_5
ADC1.SamplingTime-0\#ChannelRegularConversion=ADC_SAMPLETIME_64CYCLES_5
ADC1.SamplingTime-1\#ChannelRegularConversion=ADC_SAMPLETIME_64CYCLES_5
ADC1.master=1
ADC3.Channel-0\#ChannelRegularConversion=ADC_CHANNEL_TEMPSENSOR
ADC3.ClockPrescaler=ADC_CLOCK_ASYNC_DIV64
@@ -82,10 +82,10 @@ Dma.SPI1_RX.11.Direction=DMA_PERIPH_TO_MEMORY
Dma.SPI1_RX.11.EventEnable=DISABLE
Dma.SPI1_RX.11.FIFOMode=DMA_FIFOMODE_DISABLE
Dma.SPI1_RX.11.Instance=DMA2_Stream7
Dma.SPI1_RX.11.MemDataAlignment=DMA_MDATAALIGN_HALFWORD
Dma.SPI1_RX.11.MemDataAlignment=DMA_MDATAALIGN_BYTE
Dma.SPI1_RX.11.MemInc=DMA_MINC_ENABLE
Dma.SPI1_RX.11.Mode=DMA_NORMAL
Dma.SPI1_RX.11.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD
Dma.SPI1_RX.11.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
Dma.SPI1_RX.11.PeriphInc=DMA_PINC_DISABLE
Dma.SPI1_RX.11.Polarity=HAL_DMAMUX_REQ_GEN_RISING
Dma.SPI1_RX.11.Priority=DMA_PRIORITY_VERY_HIGH
@@ -100,10 +100,10 @@ Dma.SPI1_TX.12.Direction=DMA_MEMORY_TO_PERIPH
Dma.SPI1_TX.12.EventEnable=DISABLE
Dma.SPI1_TX.12.FIFOMode=DMA_FIFOMODE_DISABLE
Dma.SPI1_TX.12.Instance=DMA1_Stream3
Dma.SPI1_TX.12.MemDataAlignment=DMA_MDATAALIGN_HALFWORD
Dma.SPI1_TX.12.MemDataAlignment=DMA_MDATAALIGN_BYTE
Dma.SPI1_TX.12.MemInc=DMA_MINC_ENABLE
Dma.SPI1_TX.12.Mode=DMA_NORMAL
Dma.SPI1_TX.12.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD
Dma.SPI1_TX.12.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
Dma.SPI1_TX.12.PeriphInc=DMA_PINC_DISABLE
Dma.SPI1_TX.12.Polarity=HAL_DMAMUX_REQ_GEN_RISING
Dma.SPI1_TX.12.Priority=DMA_PRIORITY_VERY_HIGH
@@ -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
@@ -457,20 +461,19 @@ Mcu.IP10=NVIC
Mcu.IP11=RCC
Mcu.IP12=SPI1
Mcu.IP13=SPI2
Mcu.IP14=SPI6
Mcu.IP15=SYS
Mcu.IP16=TIM1
Mcu.IP17=TIM3
Mcu.IP18=TIM12
Mcu.IP19=UART5
Mcu.IP14=SYS
Mcu.IP15=TIM1
Mcu.IP16=TIM3
Mcu.IP17=TIM12
Mcu.IP18=UART5
Mcu.IP19=UART7
Mcu.IP2=CORTEX_M7
Mcu.IP20=UART7
Mcu.IP21=USART1
Mcu.IP22=USART2
Mcu.IP23=USART3
Mcu.IP24=USART10
Mcu.IP25=USB_DEVICE
Mcu.IP26=USB_OTG_HS
Mcu.IP20=USART1
Mcu.IP21=USART2
Mcu.IP22=USART3
Mcu.IP23=USART10
Mcu.IP24=USB_DEVICE
Mcu.IP25=USB_OTG_HS
Mcu.IP3=CRC
Mcu.IP4=DMA
Mcu.IP5=FDCAN1
@@ -478,77 +481,78 @@ Mcu.IP6=FDCAN2
Mcu.IP7=FDCAN3
Mcu.IP8=FREERTOS
Mcu.IP9=MEMORYMAP
Mcu.IPNb=27
Mcu.IPNb=26
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 +572,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
@@ -616,10 +623,8 @@ PA2.GPIO_Speed=GPIO_SPEED_FREQ_HIGH
PA2.Locked=true
PA2.PinState=GPIO_PIN_SET
PA2.Signal=GPIO_Output
PA5.Mode=TX_Only_Simplex_Unidirect_Master
PA5.Signal=SPI6_SCK
PA6.Mode=Full_Duplex_Master
PA6.Signal=SPI6_MISO
PA5.Signal=ADCx_INP19
PA6.Signal=GPIO_Input
PA7.Mode=TX_Only_Simplex_Unidirect_Master
PA7.Signal=SPI6_MOSI
PA8.Mode=Clock-out-1
@@ -648,9 +653,13 @@ PB13.Locked=true
PB13.Mode=Full_Duplex_Master
PB13.Signal=SPI2_SCK
PB15.Signal=S_TIM12_CH2
PB3(JTDO/TRACESWO).GPIOParameters=GPIO_Speed
PB3(JTDO/TRACESWO).GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH
PB3(JTDO/TRACESWO).Locked=true
PB3(JTDO/TRACESWO).Mode=Full_Duplex_Master
PB3(JTDO/TRACESWO).Signal=SPI1_SCK
PB4(NJTRST).GPIOParameters=GPIO_Speed
PB4(NJTRST).GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH
PB4(NJTRST).Locked=true
PB4(NJTRST).Mode=Full_Duplex_Master
PB4(NJTRST).Signal=SPI1_MISO
@@ -660,10 +669,11 @@ PB5.Signal=FDCAN2_RX
PB6.Locked=true
PB6.Mode=FDCAN_Activate
PB6.Signal=FDCAN2_TX
PC0.GPIOParameters=GPIO_Speed,GPIO_Label
PC0.GPIOParameters=GPIO_Speed,PinState,GPIO_Label
PC0.GPIO_Label=ACC_CS
PC0.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH
PC0.Locked=true
PC0.PinState=GPIO_PIN_SET
PC0.Signal=GPIO_Output
PC1.GPIOParameters=GPIO_Speed
PC1.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH
@@ -676,9 +686,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
@@ -721,6 +740,8 @@ PD5.Signal=USART2_TX
PD6.Locked=true
PD6.Mode=Asynchronous
PD6.Signal=USART2_RX
PD7.GPIOParameters=GPIO_Speed
PD7.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH
PD7.Locked=true
PD7.Mode=Full_Duplex_Master
PD7.Signal=SPI1_MOSI
@@ -770,7 +791,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 +801,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
@@ -799,7 +821,7 @@ ProjectManager.ToolChainLocation=
ProjectManager.UAScriptAfterPath=
ProjectManager.UAScriptBeforePath=
ProjectManager.UnderRoot=false
ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_FDCAN2_Init-FDCAN2-false-HAL-true,5-MX_FDCAN3_Init-FDCAN3-false-HAL-true,6-MX_CRC_Init-CRC-false-HAL-true,7-MX_USB_DEVICE_Init-USB_DEVICE-false-HAL-false,8-MX_FDCAN1_Init-FDCAN1-false-HAL-true,9-MX_USART10_UART_Init-USART10-false-HAL-true,10-MX_SPI2_Init-SPI2-false-HAL-true,11-MX_TIM12_Init-TIM12-false-HAL-true,12-MX_UART7_Init-UART7-false-HAL-true,13-MX_TIM1_Init-TIM1-false-HAL-true,14-MX_USART1_UART_Init-USART1-false-HAL-true,15-MX_ADC3_Init-ADC3-false-HAL-true,16-MX_USART2_UART_Init-USART2-false-HAL-true,17-MX_USART3_UART_Init-USART3-false-HAL-true,18-MX_SPI1_Init-SPI1-false-HAL-true,19-MX_UART5_Init-UART5-false-HAL-true,20-MX_TIM3_Init-TIM3-false-HAL-true,21-MX_SPI6_Init-SPI6-false-HAL-true,22-MX_ADC1_Init-ADC1-false-HAL-true,0-MX_CORTEX_M7_Init-CORTEX_M7-false-HAL-true
ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_FDCAN2_Init-FDCAN2-false-HAL-true,5-MX_FDCAN3_Init-FDCAN3-false-HAL-true,6-MX_CRC_Init-CRC-false-HAL-true,7-MX_USB_DEVICE_Init-USB_DEVICE-false-HAL-false,8-MX_FDCAN1_Init-FDCAN1-false-HAL-true,9-MX_USART10_UART_Init-USART10-false-HAL-true,10-MX_SPI2_Init-SPI2-false-HAL-true,11-MX_TIM12_Init-TIM12-false-HAL-true,12-MX_UART7_Init-UART7-false-HAL-true,13-MX_TIM1_Init-TIM1-false-HAL-true,14-MX_USART1_UART_Init-USART1-false-HAL-true,15-MX_ADC3_Init-ADC3-false-HAL-true,16-MX_USART2_UART_Init-USART2-false-HAL-true,17-MX_USART3_UART_Init-USART3-false-HAL-true,18-MX_SPI1_Init-SPI1-false-HAL-true,19-MX_UART5_Init-UART5-false-HAL-true,20-MX_TIM3_Init-TIM3-false-HAL-true,21-MX_ADC1_Init-ADC1-false-HAL-true,0-MX_CORTEX_M7_Init-CORTEX_M7-false-HAL-true
RCC.ADCFreq_Value=96000000
RCC.AHB12Freq_Value=240000000
RCC.AHB4Freq_Value=240000000
@@ -883,6 +905,8 @@ RCC.VCO3OutputFreq_Value=96750000
RCC.VCOInput1Freq_Value=12000000
RCC.VCOInput2Freq_Value=12000000
RCC.VCOInput3Freq_Value=750000
SH.ADCx_INP19.0=ADC1_INP19,IN19-Single-Ended
SH.ADCx_INP19.ConfNb=1
SH.ADCx_INP4.0=ADC1_INP4,IN4-Single-Ended
SH.ADCx_INP4.ConfNb=1
SH.GPXTI10.0=GPIO_EXTI10
@@ -896,11 +920,12 @@ SH.S_TIM3_CH4.ConfNb=1
SH.SharedStack_PC12.0=SPI3_MOSI
SH.SharedStack_PC12.1=UART5_TX,Asynchronous
SH.SharedStack_PC12.ConfNb=2
SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_16
SPI1.CalculateBaudRate=7.5 MBits/s
SPI1.DataSize=SPI_DATASIZE_16BIT
SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_2
SPI1.CLKPolarity=SPI_POLARITY_LOW
SPI1.CalculateBaudRate=60 MBits/s
SPI1.DataSize=SPI_DATASIZE_8BIT
SPI1.Direction=SPI_DIRECTION_2LINES
SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,DataSize,BaudRatePrescaler
SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,DataSize,BaudRatePrescaler,CLKPolarity
SPI1.Mode=SPI_MODE_MASTER
SPI1.VirtualType=VM_MASTER
SPI2.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_32
@@ -913,14 +938,6 @@ SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,DataSize,BaudRate
SPI2.Mode=SPI_MODE_MASTER
SPI2.NSSPMode=SPI_NSS_PULSE_ENABLE
SPI2.VirtualType=VM_MASTER
SPI6.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_4
SPI6.CLKPhase=SPI_PHASE_2EDGE
SPI6.CalculateBaudRate=6.0 MBits/s
SPI6.DataSize=SPI_DATASIZE_8BIT
SPI6.Direction=SPI_DIRECTION_2LINES_TXONLY
SPI6.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,DataSize,BaudRatePrescaler,CLKPhase
SPI6.Mode=SPI_MODE_MASTER
SPI6.VirtualType=VM_MASTER
STMicroelectronics.X-CUBE-ALGOBUILD.1.4.0.DSPOoLibraryJjLibrary_Checked=true
STMicroelectronics.X-CUBE-ALGOBUILD.1.4.0.IPParameters=LibraryCcDSPOoLibraryJjDSPOoLibrary
STMicroelectronics.X-CUBE-ALGOBUILD.1.4.0.LibraryCcDSPOoLibraryJjDSPOoLibrary=true
@@ -940,9 +957,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,10 +54,13 @@ void robotSelfCheck(void)
void ws2812Task(void *argument)
{
(void) argument;
RobotMode_t RobotMode = REMOTE_NOT_CONNECTED; // 初始状态为遥控器未连接
while (1)
{
switch (RobotMode)
RobotMode_t display_mode = RobotMode;
if (display_mode != SYS_ERROR_OCCURRED && !RemoteControlIsOnline())
display_mode = REMOTE_NOT_CONNECTED;
switch (display_mode)
{
case NORMAL_MODE:
BlinkGreen();
@@ -65,10 +69,16 @@ void ws2812Task(void *argument)
BlinkRed();
break;
case REMOTE_NOT_CONNECTED:
BlinkYellow();
BlinkRed();
break;
case REMOTE_CONNECTED:
BlinkBlue();
case REMOTE_NOT_READY:
BlinkWhite();
break;
case REMOTE_READY:
BlinkPurple();
break;
case REMOTE_PROTECT:
BlinkYellow();
break;
case AUTO_SHOOTING_MODE:
BlinkCyan();
@@ -160,7 +170,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

@@ -15,11 +15,14 @@
/* Includes ------------------------------------------------------------------*/
#include "bsp_adc.h"
#include "adc.h"
#include <stdbool.h>
/**
* @brief ADC sampling voltage array
*/
__attribute__((section (".AXI_SRAM"))) uint16_t ADC_Voltage_Val[2];
__attribute__((section(".dma_buffer"), aligned(32))) volatile uint16_t ADC_Voltage_Val[BSP_ADC_CHANNEL_COUNT];
static bool bsp_adc_started = false;
/**
* @brief Configures the ADC.
@@ -28,8 +31,16 @@ __attribute__((section (".AXI_SRAM"))) uint16_t ADC_Voltage_Val[2];
*/
void BSP_ADC_Init(void)
{
HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) ADC_Voltage_Val, 2);
if (bsp_adc_started)
{
return;
}
(void) HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);
if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *) ADC_Voltage_Val, BSP_ADC_CHANNEL_COUNT) == HAL_OK)
{
bsp_adc_started = true;
}
}
//------------------------------------------------------------------------------
@@ -41,8 +52,33 @@ void BSP_ADC_Init(void)
*/
float USER_ADC_Voltage_Update(void)
{
auto Voltage = (ADC_Voltage_Val[0] * 3.3f / 65535) * 11.0f;
return Voltage;
float voltage = ((float) ADC_Voltage_Val[BSP_ADC_BOARD_VOLTAGE_INDEX] * 3.3f / 65535.0f) * 11.0f;
return voltage;
}
//------------------------------------------------------------------------------
uint16_t BSP_ADC_GetRaw(uint32_t index)
{
if (index >= BSP_ADC_CHANNEL_COUNT)
{
return 0U;
}
return ADC_Voltage_Val[index];
}
uint16_t BSP_ADC_GetJoystickRaw(void)
{
return BSP_ADC_GetRaw(BSP_ADC_JOYSTICK_INDEX);
}
uint16_t BSP_ADC_GetJoystickRaw12(void)
{
return (uint16_t) (BSP_ADC_GetJoystickRaw() >> 4U);
}
float BSP_ADC_GetJoystickVoltage(void)
{
return (float) BSP_ADC_GetJoystickRaw() * 3.3f / 65535.0f;
}

View File

@@ -26,9 +26,20 @@ extern "C"
/* Includes ------------------------------------------------------------------*/
#include "adc.h"
#include <stdint.h>
#define BSP_ADC_CHANNEL_COUNT 2U
#define BSP_ADC_BOARD_VOLTAGE_INDEX 0U
#define BSP_ADC_JOYSTICK_INDEX 1U
extern volatile uint16_t ADC_Voltage_Val[BSP_ADC_CHANNEL_COUNT];
/* Externs ---------------------------------------------*/
void BSP_ADC_Init(void);
float USER_ADC_Voltage_Update(void);
uint16_t BSP_ADC_GetRaw(uint32_t index);
uint16_t BSP_ADC_GetJoystickRaw(void);
uint16_t BSP_ADC_GetJoystickRaw12(void);
float BSP_ADC_GetJoystickVoltage(void);
#endif //BSP_ADC_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

@@ -1,21 +1,18 @@
// #pragma once // 可以用#pragma once代替#ifndef ROBOT_DEF_H(header guard)
#ifndef ROBOT_DEF_H
#define ROBOT_DEF_H
typedef enum
{
NORMAL_MODE = 0, // 正常模式对应数值0
SYS_ERROR_OCCURRED = 1, // 系统错误对应数值1
REMOTE_NOT_CONNECTED = 2, // 遥控器未连接对应数值2
REMOTE_CONNECTED = 3, // 遥控器已连接对应数值3
AUTO_SHOOTING_MODE = 4, // 成功进入自动瞄准模式对应数值4
IMU_CALIBERATION_MODE = 5 // IMU校准模式对应数值5
// 添加其他模式
} RobotMode_t; //机器人控制模式
NORMAL_MODE = 0,
SYS_ERROR_OCCURRED = 1,
REMOTE_NOT_CONNECTED = 2,
REMOTE_NOT_READY = 3,
REMOTE_READY = 4,
REMOTE_PROTECT = 5,
AUTO_SHOOTING_MODE = 6,
IMU_CALIBERATION_MODE = 7
} RobotMode_t;
extern RobotMode_t RobotMode;
// #pragma pack() // 开启字节对齐,结束前面的#pragma pack(1)
extern volatile RobotMode_t RobotMode;
#endif

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,74 @@
#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;
}
static uint8_t RemoteControlAllSwitchesDown(const RC_ctrl_t *rc)
{
return rc != NULL && switch_is_down(rc->sw_a) && switch_is_down(rc->sw_b) && switch_is_down(rc->sw_c) &&
switch_is_down(rc->sw_d);
}
static void RemoteControlSetMode(RobotMode_t mode)
{
if (RobotMode != SYS_ERROR_OCCURRED)
RobotMode = mode;
}
static void RemoteControlUpdateMode(const RC_ctrl_t *rc)
{
if (rc == NULL || RobotMode == SYS_ERROR_OCCURRED)
return;
switch (RobotMode)
{
case REMOTE_NOT_CONNECTED:
RemoteControlSetMode(REMOTE_NOT_READY);
break;
case REMOTE_NOT_READY:
if (RemoteControlAllSwitchesDown(rc))
RemoteControlSetMode(REMOTE_READY);
break;
case REMOTE_READY:
if (switch_is_up(rc->sw_d))
RemoteControlSetMode(NORMAL_MODE);
break;
case NORMAL_MODE:
if (switch_is_down(rc->sw_d))
RemoteControlSetMode(REMOTE_PROTECT);
break;
case REMOTE_PROTECT:
if (switch_is_up(rc->sw_d))
RemoteControlSetMode(NORMAL_MODE);
break;
default:
break;
}
}
#ifdef REMOTE_DJI_DT7
/**
@@ -112,8 +163,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 +223,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 +233,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 +285,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 +319,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 +339,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 +367,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 +393,52 @@ 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));
RemoteControlSetMode(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);
RemoteControlUpdateMode(&rc_ctrl[TEMP]);
}
/**
@@ -393,38 +447,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

@@ -0,0 +1,822 @@
#include "tft.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#define TFT_SPI_TIMEOUT_MS 100U
#define TFT_FONT_W 5U
#define TFT_FONT_H 7U
#define TFT_MAX_SCALE 4U
#if (TFT_USE_HORIZONTAL == 0U) || (TFT_USE_HORIZONTAL == 1U)
#define TFT_X_OFFSET 0U
#define TFT_Y_OFFSET 20U
#else
#define TFT_X_OFFSET 20U
#define TFT_Y_OFFSET 0U
#endif
typedef enum
{
TFT_GLYPH_SPACE = 0,
TFT_GLYPH_DASH,
TFT_GLYPH_DOT,
TFT_GLYPH_COLON,
TFT_GLYPH_0,
TFT_GLYPH_A = TFT_GLYPH_0 + 10
} TFT_GlyphIndex_e;
static bool tft_initialized = false;
static bool tft_transfer_ok = true;
static volatile bool tft_dma_active = false;
static TFT_TransferDoneCallback tft_dma_callback = NULL;
static void *tft_dma_context = NULL;
static const uint8_t tft_font5x7[][TFT_FONT_H] = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* space */
{0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}, /* - */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C}, /* . */
{0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00}, /* : */
{0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, /* 0 */
{0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x0E}, /* 1 */
{0x0E, 0x11, 0x10, 0x08, 0x04, 0x02, 0x1F}, /* 2 */
{0x1F, 0x08, 0x04, 0x08, 0x10, 0x11, 0x0E}, /* 3 */
{0x08, 0x0C, 0x0A, 0x09, 0x1F, 0x08, 0x08}, /* 4 */
{0x1F, 0x01, 0x0F, 0x10, 0x10, 0x11, 0x0E}, /* 5 */
{0x0C, 0x02, 0x01, 0x0F, 0x11, 0x11, 0x0E}, /* 6 */
{0x1F, 0x10, 0x08, 0x04, 0x02, 0x02, 0x02}, /* 7 */
{0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, /* 8 */
{0x0E, 0x11, 0x11, 0x1E, 0x10, 0x08, 0x06}, /* 9 */
{0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, /* A */
{0x0F, 0x11, 0x11, 0x0F, 0x11, 0x11, 0x0F}, /* B */
{0x0E, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0E}, /* C */
{0x0F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0F}, /* D */
{0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x1F}, /* E */
{0x1F, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x01}, /* F */
{0x0E, 0x11, 0x01, 0x1D, 0x11, 0x11, 0x0E}, /* G */
{0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, /* H */
{0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, /* I */
{0x1C, 0x08, 0x08, 0x08, 0x08, 0x09, 0x06}, /* J */
{0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11}, /* K */
{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1F}, /* L */
{0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}, /* M */
{0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x11}, /* N */
{0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, /* O */
{0x0F, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x01}, /* P */
{0x0E, 0x11, 0x11, 0x11, 0x15, 0x09, 0x16}, /* Q */
{0x0F, 0x11, 0x11, 0x0F, 0x05, 0x09, 0x11}, /* R */
{0x1E, 0x01, 0x01, 0x0E, 0x10, 0x10, 0x0F}, /* S */
{0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}, /* T */
{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, /* U */
{0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04}, /* V */
{0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A}, /* W */
{0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11}, /* X */
{0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04}, /* Y */
{0x1F, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1F}, /* Z */
};
static void tft_cs_low(void)
{
HAL_GPIO_WritePin(TFT_CS_GPIO_Port, TFT_CS_Pin, GPIO_PIN_RESET);
}
static void tft_cs_high(void)
{
HAL_GPIO_WritePin(TFT_CS_GPIO_Port, TFT_CS_Pin, GPIO_PIN_SET);
}
static void tft_dc_command(void)
{
HAL_GPIO_WritePin(TFT_DC_GPIO_Port, TFT_DC_Pin, GPIO_PIN_RESET);
}
static void tft_dc_data(void)
{
HAL_GPIO_WritePin(TFT_DC_GPIO_Port, TFT_DC_Pin, GPIO_PIN_SET);
}
static void tft_reset_low(void)
{
HAL_GPIO_WritePin(TFT_RES_GPIO_Port, TFT_RES_Pin, GPIO_PIN_RESET);
}
static void tft_reset_high(void)
{
HAL_GPIO_WritePin(TFT_RES_GPIO_Port, TFT_RES_Pin, GPIO_PIN_SET);
}
static void tft_backlight_on(void)
{
HAL_GPIO_WritePin(TFT_BLK_GPIO_Port, TFT_BLK_Pin, GPIO_PIN_SET);
}
static void tft_dma_finish(bool ok)
{
TFT_TransferDoneCallback callback = tft_dma_callback;
void *context = tft_dma_context;
if (!tft_dma_active)
{
return;
}
tft_dma_active = false;
tft_dma_callback = NULL;
tft_dma_context = NULL;
if (!ok)
{
tft_transfer_ok = false;
}
tft_cs_high();
if (callback != NULL)
{
callback(context, ok);
}
}
static void tft_bus_init(void)
{
#if TFT_USE_SOFT_SPI
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
GPIO_InitStruct.Pin = TFT_SCK_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(TFT_SCK_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TFT_MOSI_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(TFT_MOSI_GPIO_Port, &GPIO_InitStruct);
HAL_GPIO_WritePin(TFT_SCK_GPIO_Port, TFT_SCK_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(TFT_MOSI_GPIO_Port, TFT_MOSI_Pin, GPIO_PIN_RESET);
#endif
}
#if TFT_USE_SOFT_SPI
static void tft_write_byte(uint8_t data)
{
for (uint8_t i = 0U; i < 8U; i++)
{
HAL_GPIO_WritePin(TFT_SCK_GPIO_Port, TFT_SCK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(TFT_MOSI_GPIO_Port, TFT_MOSI_Pin,
(data & 0x80U) != 0U ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(TFT_SCK_GPIO_Port, TFT_SCK_Pin, GPIO_PIN_SET);
data <<= 1U;
}
}
#endif
static void tft_write_bytes(const uint8_t *data, uint32_t len)
{
#if TFT_USE_SOFT_SPI
while (len > 0U)
{
tft_write_byte(*data);
data++;
len--;
}
#else
while (len > 0U)
{
#if TFT_HW_SPI_BYTE_MODE && TFT_HW_SPI_PULSE_CS
tft_cs_low();
if (HAL_SPI_Transmit(&TFT_SPI_UNIT, (uint8_t *) data, 1U, TFT_SPI_TIMEOUT_MS) != HAL_OK)
{
tft_transfer_ok = false;
tft_cs_high();
return;
}
tft_cs_high();
data++;
len--;
#else
#if TFT_HW_SPI_BYTE_MODE
uint16_t chunk = 1U;
#else
uint16_t chunk = len > UINT16_MAX ? UINT16_MAX : (uint16_t) len;
#endif
if (HAL_SPI_Transmit(&TFT_SPI_UNIT, (uint8_t *) data, chunk, TFT_SPI_TIMEOUT_MS) != HAL_OK)
{
tft_transfer_ok = false;
tft_cs_high();
return;
}
data += chunk;
len -= chunk;
#endif
}
#endif
}
static void tft_write_bytes_continuous(const uint8_t *data, uint32_t len)
{
#if TFT_USE_SOFT_SPI
tft_write_bytes(data, len);
#else
while (len > 0U)
{
uint16_t chunk = len > UINT16_MAX ? UINT16_MAX : (uint16_t) len;
if (HAL_SPI_Transmit(&TFT_SPI_UNIT, (uint8_t *) data, chunk, TFT_SPI_TIMEOUT_MS) != HAL_OK)
{
tft_transfer_ok = false;
return;
}
data += chunk;
len -= chunk;
}
#endif
}
static void lcd_write_reg(uint8_t reg)
{
tft_cs_low();
tft_dc_command();
tft_write_bytes(&reg, 1U);
tft_cs_high();
tft_dc_data();
}
static void lcd_write_data8(uint8_t data)
{
tft_cs_low();
tft_dc_data();
tft_write_bytes(&data, 1U);
tft_cs_high();
}
static void lcd_write_reg_data(uint8_t reg, const uint8_t *data, uint32_t len)
{
tft_cs_low();
tft_dc_command();
tft_write_bytes(&reg, 1U);
if (data != NULL && len > 0U && tft_transfer_ok)
{
tft_dc_data();
tft_write_bytes(data, len);
}
tft_cs_high();
tft_dc_data();
}
static void tft_write_color_block(uint16_t color, uint32_t count)
{
uint8_t buf[64];
uint8_t high = (uint8_t) (color >> 8);
uint8_t low = (uint8_t) color;
for (uint32_t i = 0U; i < sizeof(buf); i += 2U)
{
buf[i] = high;
buf[i + 1U] = low;
}
tft_cs_low();
tft_dc_data();
while (count > 0U && tft_transfer_ok)
{
uint32_t pixels = count > (sizeof(buf) / 2U) ? (sizeof(buf) / 2U) : count;
tft_write_bytes(buf, pixels * 2U);
count -= pixels;
}
tft_cs_high();
}
void TFT_WriteColorData(const uint8_t *data, uint32_t len)
{
if (data == NULL || len == 0U)
{
return;
}
tft_cs_low();
tft_dc_data();
#if TFT_HW_SPI_STREAM_COLOR
tft_write_bytes_continuous(data, len);
#else
tft_write_bytes(data, len);
#endif
tft_cs_high();
}
bool TFT_WriteColorDataDMA(const uint8_t *data,
uint32_t len,
TFT_TransferDoneCallback callback,
void *context)
{
if (data == NULL || len == 0U || len > UINT16_MAX || tft_dma_active || TFT_SPI_UNIT.hdmatx == NULL)
{
return false;
}
tft_cs_low();
tft_dc_data();
tft_dma_callback = callback;
tft_dma_context = context;
tft_dma_active = true;
if (HAL_SPI_Transmit_DMA(&TFT_SPI_UNIT, (uint8_t *) data, (uint16_t) len) != HAL_OK)
{
tft_dma_active = false;
tft_dma_callback = NULL;
tft_dma_context = NULL;
tft_transfer_ok = false;
tft_cs_high();
return false;
}
return true;
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi == &TFT_SPI_UNIT)
{
tft_dma_finish(true);
}
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi == &TFT_SPI_UNIT)
{
tft_dma_finish(false);
}
}
bool TFT_IsReady(void)
{
return tft_initialized && tft_transfer_ok;
}
void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
uint16_t xs = x1 + TFT_X_OFFSET;
uint16_t xe = x2 + TFT_X_OFFSET;
uint16_t ys = y1 + TFT_Y_OFFSET;
uint16_t ye = y2 + TFT_Y_OFFSET;
uint8_t col_data[4] = {
(uint8_t) (xs >> 8),
(uint8_t) xs,
(uint8_t) (xe >> 8),
(uint8_t) xe
};
uint8_t row_data[4] = {
(uint8_t) (ys >> 8),
(uint8_t) ys,
(uint8_t) (ye >> 8),
(uint8_t) ye
};
lcd_write_reg_data(0x2A, col_data, sizeof(col_data));
lcd_write_reg_data(0x2B, row_data, sizeof(row_data));
lcd_write_reg_data(0x2C, NULL, 0U);
}
void TFT_FillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color)
{
if (x >= TFT_LCD_W || y >= TFT_LCD_H || width == 0U || height == 0U)
{
return;
}
if ((uint32_t) x + width > TFT_LCD_W)
{
width = (uint16_t) (TFT_LCD_W - x);
}
if ((uint32_t) y + height > TFT_LCD_H)
{
height = (uint16_t) (TFT_LCD_H - y);
}
LCD_Address_Set(x, y, (uint16_t) (x + width - 1U), (uint16_t) (y + height - 1U));
tft_write_color_block(color, (uint32_t) width * height);
}
void LCD_Fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color)
{
if (xend <= xsta || yend <= ysta)
{
return;
}
TFT_FillRect(xsta, ysta, (uint16_t) (xend - xsta), (uint16_t) (yend - ysta), color);
}
void TFT_DrawPoint(uint16_t x, uint16_t y, uint16_t color)
{
TFT_FillRect(x, y, 1U, 1U, color);
}
static const uint8_t *tft_get_glyph(char ch)
{
if (ch >= 'a' && ch <= 'z')
{
ch = (char) (ch - ('a' - 'A'));
}
if (ch >= '0' && ch <= '9')
{
return tft_font5x7[TFT_GLYPH_0 + (uint8_t) (ch - '0')];
}
if (ch >= 'A' && ch <= 'Z')
{
return tft_font5x7[TFT_GLYPH_A + (uint8_t) (ch - 'A')];
}
switch (ch)
{
case '-':
return tft_font5x7[TFT_GLYPH_DASH];
case '.':
return tft_font5x7[TFT_GLYPH_DOT];
case ':':
return tft_font5x7[TFT_GLYPH_COLON];
default:
return tft_font5x7[TFT_GLYPH_SPACE];
}
}
static void tft_draw_char(uint16_t x, uint16_t y, char ch, uint16_t fc, uint16_t bc, uint8_t scale)
{
uint8_t line[(TFT_FONT_W * TFT_MAX_SCALE) * 2U];
const uint8_t *glyph;
uint16_t char_w;
uint16_t char_h;
if (scale == 0U)
{
scale = 1U;
}
if (scale > TFT_MAX_SCALE)
{
scale = TFT_MAX_SCALE;
}
char_w = TFT_FONT_W * scale;
char_h = TFT_FONT_H * scale;
if (x >= TFT_LCD_W || y >= TFT_LCD_H)
{
return;
}
glyph = tft_get_glyph(ch);
LCD_Address_Set(x, y, (uint16_t) (x + char_w - 1U), (uint16_t) (y + char_h - 1U));
tft_cs_low();
tft_dc_data();
for (uint8_t row = 0U; row < TFT_FONT_H; row++)
{
for (uint8_t repeat_y = 0U; repeat_y < scale; repeat_y++)
{
uint32_t idx = 0U;
for (uint8_t col = 0U; col < TFT_FONT_W; col++)
{
uint16_t color = (glyph[row] & (1U << col)) ? fc : bc;
for (uint8_t repeat_x = 0U; repeat_x < scale; repeat_x++)
{
line[idx++] = (uint8_t) (color >> 8);
line[idx++] = (uint8_t) color;
}
}
tft_write_bytes(line, idx);
}
}
tft_cs_high();
}
void TFT_DrawString(uint16_t x, uint16_t y, const char *text, uint16_t fc, uint16_t bc, uint8_t scale)
{
uint16_t cursor = x;
uint16_t advance;
if (text == NULL)
{
return;
}
if (scale == 0U)
{
scale = 1U;
}
if (scale > TFT_MAX_SCALE)
{
scale = TFT_MAX_SCALE;
}
advance = (TFT_FONT_W + 1U) * scale;
while (*text != '\0')
{
if (cursor + (TFT_FONT_W * scale) >= TFT_LCD_W)
{
break;
}
tft_draw_char(cursor, y, *text, fc, bc, scale);
cursor = (uint16_t) (cursor + advance);
text++;
}
}
void TFT_Clear(uint16_t color)
{
LCD_Fill(0U, 0U, TFT_LCD_W, TFT_LCD_H, color);
}
void LCD_Init(void)
{
if (tft_initialized)
{
return;
}
tft_transfer_ok = true;
tft_bus_init();
tft_cs_high();
tft_reset_low();
HAL_Delay(100);
tft_reset_high();
HAL_Delay(100);
tft_backlight_on();
HAL_Delay(100);
lcd_write_reg(0x11);
HAL_Delay(120);
lcd_write_reg(0x36);
#if TFT_USE_HORIZONTAL == 0U
lcd_write_data8(0x00);
#elif TFT_USE_HORIZONTAL == 1U
lcd_write_data8(0xC0);
#elif TFT_USE_HORIZONTAL == 2U
lcd_write_data8(0x70);
#else
lcd_write_data8(0xA0);
#endif
lcd_write_reg(0x3A);
lcd_write_data8(0x05);
lcd_write_reg(0xB2);
lcd_write_data8(0x0C);
lcd_write_data8(0x0C);
lcd_write_data8(0x00);
lcd_write_data8(0x33);
lcd_write_data8(0x33);
lcd_write_reg(0xB7);
lcd_write_data8(0x35);
lcd_write_reg(0xBB);
lcd_write_data8(0x32);
lcd_write_reg(0xC2);
lcd_write_data8(0x01);
lcd_write_reg(0xC3);
lcd_write_data8(0x15);
lcd_write_reg(0xC4);
lcd_write_data8(0x20);
lcd_write_reg(0xC6);
lcd_write_data8(0x0F);
lcd_write_reg(0xD0);
lcd_write_data8(0xA4);
lcd_write_data8(0xA1);
lcd_write_reg(0xE0);
lcd_write_data8(0xD0);
lcd_write_data8(0x08);
lcd_write_data8(0x0E);
lcd_write_data8(0x09);
lcd_write_data8(0x09);
lcd_write_data8(0x05);
lcd_write_data8(0x31);
lcd_write_data8(0x33);
lcd_write_data8(0x48);
lcd_write_data8(0x17);
lcd_write_data8(0x14);
lcd_write_data8(0x15);
lcd_write_data8(0x31);
lcd_write_data8(0x34);
lcd_write_reg(0xE1);
lcd_write_data8(0xD0);
lcd_write_data8(0x08);
lcd_write_data8(0x0E);
lcd_write_data8(0x09);
lcd_write_data8(0x09);
lcd_write_data8(0x15);
lcd_write_data8(0x31);
lcd_write_data8(0x33);
lcd_write_data8(0x48);
lcd_write_data8(0x17);
lcd_write_data8(0x14);
lcd_write_data8(0x15);
lcd_write_data8(0x31);
lcd_write_data8(0x34);
lcd_write_reg(0x21);
lcd_write_reg(0x29);
TFT_Clear(TFT_COLOR_BLACK);
if (tft_transfer_ok)
{
tft_initialized = true;
}
}
void TFT_Init(void)
{
LCD_Init();
}
void TFT_ShowBootScreen(void)
{
if (!tft_initialized)
{
TFT_Init();
if (!tft_initialized)
{
return;
}
}
tft_transfer_ok = true;
TFT_Clear(TFT_COLOR_BLACK);
TFT_DrawString(20U, 80U, "TronOneH7-Scaffold", TFT_COLOR_WHITE, TFT_COLOR_BLACK, 2U);
TFT_DrawString(107U, 130U, "Starting...", TFT_COLOR_WHITE, TFT_COLOR_BLACK, 1U);
}
const char *TFT_RobotModeText(RobotMode_t mode)
{
switch (mode)
{
case NORMAL_MODE:
return "NORMAL";
case SYS_ERROR_OCCURRED:
return "ERROR";
case REMOTE_NOT_CONNECTED:
return "REMOTE OFF";
case REMOTE_NOT_READY:
return "NOT READY";
case REMOTE_READY:
return "READY";
case REMOTE_PROTECT:
return "PROTECT";
case AUTO_SHOOTING_MODE:
return "AUTO SHOOT";
case IMU_CALIBERATION_MODE:
return "IMU CAL";
default:
return "UNKNOWN";
}
}
static uint16_t tft_mode_color(RobotMode_t mode)
{
switch (mode)
{
case NORMAL_MODE:
return TFT_COLOR_GREEN;
case SYS_ERROR_OCCURRED:
case REMOTE_NOT_CONNECTED:
return TFT_COLOR_RED;
case REMOTE_NOT_READY:
return TFT_COLOR_WHITE;
case REMOTE_READY:
return TFT_COLOR_MAGENTA;
case REMOTE_PROTECT:
return TFT_COLOR_YELLOW;
case AUTO_SHOOTING_MODE:
return TFT_COLOR_CYAN;
case IMU_CALIBERATION_MODE:
return TFT_COLOR_MAGENTA;
default:
return TFT_COLOR_LIGHT_GRAY;
}
}
static int16_t tft_temperature_to_tenths(float temperature_c)
{
return (int16_t) ((temperature_c * 10.0f) + (temperature_c >= 0.0f ? 0.5f : -0.5f));
}
static void tft_format_temperature(char *buf, size_t len, int16_t temperature_tenths)
{
uint16_t abs_temp;
uint16_t integer;
uint8_t decimal;
size_t idx = 0U;
char digits[5];
uint8_t digits_len = 0U;
if (buf == NULL || len < 6U)
{
return;
}
if (temperature_tenths < 0)
{
abs_temp = (uint16_t) (-temperature_tenths);
buf[idx++] = '-';
}
else
{
abs_temp = (uint16_t) temperature_tenths;
}
integer = abs_temp / 10U;
decimal = (uint8_t) (abs_temp % 10U);
do
{
digits[digits_len++] = (char) ('0' + (integer % 10U));
integer /= 10U;
} while (integer > 0U && digits_len < sizeof(digits));
while (digits_len > 0U && idx + 1U < len)
{
buf[idx++] = digits[--digits_len];
}
if (idx + 4U < len)
{
buf[idx++] = '.';
buf[idx++] = (char) ('0' + decimal);
buf[idx++] = ' ';
buf[idx++] = 'C';
}
buf[idx] = '\0';
}
static void tft_draw_status_layout(void)
{
TFT_Clear(TFT_COLOR_BLACK);
TFT_DrawString(16U, 16U, "DM LCD", TFT_COLOR_CYAN, TFT_COLOR_BLACK, 3U);
TFT_FillRect(16U, 54U, 248U, 2U, TFT_COLOR_DARK_GRAY);
TFT_DrawString(16U, 76U, "TEMP:", TFT_COLOR_YELLOW, TFT_COLOR_BLACK, 2U);
TFT_DrawString(16U, 132U, "MODE:", TFT_COLOR_YELLOW, TFT_COLOR_BLACK, 2U);
}
void TFT_ShowStatus(float temperature_c, RobotMode_t mode)
{
static bool layout_ready = false;
static int16_t last_temperature_tenths = INT16_MIN;
static RobotMode_t last_mode = (RobotMode_t) 0xFF;
int16_t temperature_tenths = tft_temperature_to_tenths(temperature_c);
char temperature_text[16];
const char *mode_text = TFT_RobotModeText(mode);
if (!tft_initialized)
{
TFT_Init();
if (!tft_initialized)
{
return;
}
}
if (!layout_ready)
{
tft_transfer_ok = true;
tft_draw_status_layout();
if (!tft_transfer_ok)
{
return;
}
layout_ready = true;
}
if (temperature_tenths != last_temperature_tenths)
{
tft_transfer_ok = true;
uint16_t temperature_color = temperature_tenths >= 550 ? TFT_COLOR_RED : TFT_COLOR_WHITE;
tft_format_temperature(temperature_text, sizeof(temperature_text), temperature_tenths);
TFT_FillRect(96U, 68U, 168U, 32U, TFT_COLOR_BLACK);
TFT_DrawString(100U, 70U, temperature_text, temperature_color, TFT_COLOR_BLACK, 3U);
if (tft_transfer_ok)
{
last_temperature_tenths = temperature_tenths;
}
}
if (mode != last_mode)
{
tft_transfer_ok = true;
TFT_FillRect(96U, 124U, 184U, 32U, TFT_COLOR_BLACK);
TFT_DrawString(100U, 126U, mode_text, tft_mode_color(mode), TFT_COLOR_BLACK, 3U);
if (tft_transfer_ok)
{
last_mode = mode;
}
}
}

View File

@@ -0,0 +1,110 @@
#ifndef TFT_H
#define TFT_H
#include "main.h"
#include "robot_def.h"
#include "spi.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*TFT_TransferDoneCallback)(void *context, bool ok);
#define TFT_USE_HORIZONTAL 2U
#define TFT_USE_SOFT_SPI 0U
#define TFT_HW_SPI_BYTE_MODE 0U
#define TFT_HW_SPI_PULSE_CS 0U
#define TFT_HW_SPI_STREAM_COLOR 1U
#if (TFT_USE_HORIZONTAL == 0U) || (TFT_USE_HORIZONTAL == 1U)
#define TFT_LCD_W 240U
#define TFT_LCD_H 280U
#else
#define TFT_LCD_W 280U
#define TFT_LCD_H 240U
#endif
#ifndef TFT_SPI_UNIT
#define TFT_SPI_UNIT hspi1
#endif
#ifndef TFT_SCK_GPIO_Port
#define TFT_SCK_GPIO_Port GPIOB
#define TFT_SCK_Pin GPIO_PIN_3
#endif
#ifndef TFT_MOSI_GPIO_Port
#define TFT_MOSI_GPIO_Port GPIOD
#define TFT_MOSI_Pin GPIO_PIN_7
#endif
#if defined(LCD_CS_GPIO_Port) && defined(LCD_CS_Pin)
#define TFT_CS_GPIO_Port LCD_CS_GPIO_Port
#define TFT_CS_Pin LCD_CS_Pin
#else
#define TFT_CS_GPIO_Port cs2_GPIO_Port
#define TFT_CS_Pin cs2_Pin
#endif
#if defined(LCD_BLK_GPIO_Port) && defined(LCD_BLK_Pin)
#define TFT_BLK_GPIO_Port LCD_BLK_GPIO_Port
#define TFT_BLK_Pin LCD_BLK_Pin
#else
#define TFT_BLK_GPIO_Port cs1_GPIO_Port
#define TFT_BLK_Pin cs1_Pin
#endif
#if defined(LCD_RES_GPIO_Port) && defined(LCD_RES_Pin)
#define TFT_RES_GPIO_Port LCD_RES_GPIO_Port
#define TFT_RES_Pin LCD_RES_Pin
#else
#define TFT_RES_GPIO_Port cs4_GPIO_Port
#define TFT_RES_Pin cs4_Pin
#endif
#if defined(LCD_DC_GPIO_Port) && defined(LCD_DC_Pin)
#define TFT_DC_GPIO_Port LCD_DC_GPIO_Port
#define TFT_DC_Pin LCD_DC_Pin
#else
#define TFT_DC_GPIO_Port cs3_GPIO_Port
#define TFT_DC_Pin cs3_Pin
#endif
#define TFT_COLOR_WHITE 0xFFFFU
#define TFT_COLOR_BLACK 0x0000U
#define TFT_COLOR_BLUE 0x001FU
#define TFT_COLOR_RED 0xF800U
#define TFT_COLOR_GREEN 0x07E0U
#define TFT_COLOR_CYAN 0x7FFFU
#define TFT_COLOR_YELLOW 0xFFE0U
#define TFT_COLOR_MAGENTA 0xF81FU
#define TFT_COLOR_DARK_GRAY 0x4208U
#define TFT_COLOR_LIGHT_GRAY 0xC618U
void TFT_Init(void);
void TFT_ShowBootScreen(void);
void TFT_Clear(uint16_t color);
void TFT_FillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color);
void TFT_DrawPoint(uint16_t x, uint16_t y, uint16_t color);
void TFT_DrawString(uint16_t x, uint16_t y, const char *text, uint16_t fc, uint16_t bc, uint8_t scale);
void TFT_ShowStatus(float temperature_c, RobotMode_t mode);
bool TFT_IsReady(void);
void TFT_WriteColorData(const uint8_t *data, uint32_t len);
bool TFT_WriteColorDataDMA(const uint8_t *data,
uint32_t len,
TFT_TransferDoneCallback callback,
void *context);
const char *TFT_RobotModeText(RobotMode_t mode);
void LCD_Init(void);
void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void LCD_Fill(uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,366 @@
#include "w25q64.h"
#include <string.h>
static W25Q64_Handle_t w25q64 = {0};
static bool w25q64_attached = false;
static bool w25q64_ready = false;
static void w25q64_cs_low(void)
{
if (w25q64_attached)
{
HAL_GPIO_WritePin(w25q64.cs_port, w25q64.cs_pin, GPIO_PIN_RESET);
}
}
static void w25q64_cs_high(void)
{
if (w25q64_attached)
{
HAL_GPIO_WritePin(w25q64.cs_port, w25q64.cs_pin, GPIO_PIN_SET);
}
}
static W25Q64_Status_t w25q64_spi_tx(const uint8_t *data, uint16_t len)
{
if (!w25q64_attached || w25q64.hspi == NULL)
{
return W25Q64_ERR_NOT_ATTACHED;
}
if (HAL_SPI_Transmit(w25q64.hspi, (uint8_t *) data, len, w25q64.timeout_ms) != HAL_OK)
{
return W25Q64_ERR_HAL;
}
return W25Q64_OK;
}
static W25Q64_Status_t w25q64_spi_rx(uint8_t *data, uint16_t len)
{
if (!w25q64_attached || w25q64.hspi == NULL)
{
return W25Q64_ERR_NOT_ATTACHED;
}
if (HAL_SPI_Receive(w25q64.hspi, data, len, w25q64.timeout_ms) != HAL_OK)
{
return W25Q64_ERR_HAL;
}
return W25Q64_OK;
}
static W25Q64_Status_t w25q64_write_enable(void)
{
uint8_t cmd = W25Q64_CMD_WRITE_ENABLE;
w25q64_cs_low();
if (w25q64_spi_tx(&cmd, 1U) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
w25q64_cs_high();
return W25Q64_OK;
}
static W25Q64_Status_t w25q64_read_status(uint8_t *status)
{
uint8_t cmd = W25Q64_CMD_READ_STATUS_1;
if (status == NULL)
{
return W25Q64_ERR_PARAM;
}
w25q64_cs_low();
if (w25q64_spi_tx(&cmd, 1U) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
if (w25q64_spi_rx(status, 1U) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
w25q64_cs_high();
return W25Q64_OK;
}
static W25Q64_Status_t w25q64_wait_ready(uint32_t timeout_ms)
{
uint8_t status = 0;
uint32_t start = HAL_GetTick();
do
{
if (w25q64_read_status(&status) != W25Q64_OK)
{
return W25Q64_ERR_HAL;
}
if ((status & 0x01U) == 0U)
{
return W25Q64_OK;
}
} while ((HAL_GetTick() - start) < timeout_ms);
return W25Q64_ERR_BUSY;
}
static W25Q64_Status_t w25q64_command_addr(uint8_t cmd, uint32_t addr)
{
uint8_t header[4] = {
cmd,
(uint8_t) (addr >> 16),
(uint8_t) (addr >> 8),
(uint8_t) addr
};
w25q64_cs_low();
if (w25q64_spi_tx(header, sizeof(header)) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
return W25Q64_OK;
}
void W25Q64_Attach(W25Q64_Handle_t *handle)
{
if (handle == NULL)
{
W25Q64_Detach();
return;
}
w25q64 = *handle;
w25q64_attached = (w25q64.hspi != NULL) && (w25q64.cs_port != NULL);
w25q64.timeout_ms = (w25q64.timeout_ms == 0U) ? 100U : w25q64.timeout_ms;
if (w25q64_attached)
{
HAL_GPIO_WritePin(w25q64.cs_port, w25q64.cs_pin, GPIO_PIN_SET);
}
w25q64_ready = false;
}
void W25Q64_Detach(void)
{
memset(&w25q64, 0, sizeof(w25q64));
w25q64_attached = false;
w25q64_ready = false;
}
W25Q64_Status_t W25Q64_ReadID(uint32_t *jedec_id)
{
uint8_t cmd = W25Q64_CMD_READ_ID;
uint8_t id[3] = {0};
if (jedec_id == NULL)
{
return W25Q64_ERR_PARAM;
}
if (!w25q64_attached)
{
return W25Q64_ERR_NOT_ATTACHED;
}
w25q64_cs_low();
if (w25q64_spi_tx(&cmd, 1U) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
if (w25q64_spi_rx(id, 3U) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
w25q64_cs_high();
*jedec_id = ((uint32_t) id[0] << 16) | ((uint32_t) id[1] << 8) | id[2];
return W25Q64_OK;
}
W25Q64_Status_t W25Q64_Init(void)
{
uint32_t jedec_id = 0;
if (!w25q64_attached)
{
return W25Q64_ERR_NOT_ATTACHED;
}
if (W25Q64_ReadID(&jedec_id) != W25Q64_OK)
{
w25q64_ready = false;
return W25Q64_ERR_HAL;
}
w25q64_ready = (jedec_id == W25Q64_JEDEC_ID);
return w25q64_ready ? W25Q64_OK : W25Q64_ERR_ID;
}
W25Q64_Status_t W25Q64_Read(uint32_t addr, uint8_t *data, uint32_t len)
{
W25Q64_Status_t ret;
uint32_t offset = 0U;
if (data == NULL || len == 0U)
{
return W25Q64_ERR_PARAM;
}
if (!w25q64_ready)
{
return W25Q64_ERR_NOT_ATTACHED;
}
ret = w25q64_command_addr(W25Q64_CMD_READ_DATA, addr);
if (ret != W25Q64_OK)
{
return ret;
}
while (offset < len)
{
uint16_t chunk = (uint16_t) ((len - offset) > UINT16_MAX ? UINT16_MAX : (len - offset));
ret = w25q64_spi_rx(data + offset, chunk);
if (ret != W25Q64_OK)
{
w25q64_cs_high();
return ret;
}
offset += chunk;
}
w25q64_cs_high();
return W25Q64_OK;
}
static W25Q64_Status_t w25q64_page_program(uint32_t addr, const uint8_t *data, uint16_t len)
{
W25Q64_Status_t ret;
ret = w25q64_write_enable();
if (ret != W25Q64_OK)
{
return ret;
}
ret = w25q64_command_addr(W25Q64_CMD_PAGE_PROGRAM, addr);
if (ret != W25Q64_OK)
{
return ret;
}
if (w25q64_spi_tx(data, len) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
w25q64_cs_high();
return w25q64_wait_ready(w25q64.timeout_ms);
}
W25Q64_Status_t W25Q64_Write(uint32_t addr, const uint8_t *data, uint32_t len)
{
uint32_t offset = 0U;
if (data == NULL || len == 0U)
{
return W25Q64_ERR_PARAM;
}
if (!w25q64_ready)
{
return W25Q64_ERR_NOT_ATTACHED;
}
while (offset < len)
{
uint32_t page_offset = (addr + offset) % W25Q64_PAGE_SIZE;
uint32_t room = W25Q64_PAGE_SIZE - page_offset;
uint16_t chunk = (uint16_t) ((len - offset) < room ? (len - offset) : room);
W25Q64_Status_t ret = w25q64_page_program(addr + offset, data + offset, chunk);
if (ret != W25Q64_OK)
{
return ret;
}
offset += chunk;
}
return W25Q64_OK;
}
W25Q64_Status_t W25Q64_EraseSector(uint32_t addr)
{
W25Q64_Status_t ret;
if (!w25q64_ready)
{
return W25Q64_ERR_NOT_ATTACHED;
}
ret = w25q64_write_enable();
if (ret != W25Q64_OK)
{
return ret;
}
ret = w25q64_command_addr(W25Q64_CMD_SECTOR_ERASE, addr);
if (ret != W25Q64_OK)
{
return ret;
}
w25q64_cs_high();
return w25q64_wait_ready(400U);
}
W25Q64_Status_t W25Q64_Erase64K(uint32_t addr)
{
W25Q64_Status_t ret;
if (!w25q64_ready)
{
return W25Q64_ERR_NOT_ATTACHED;
}
ret = w25q64_write_enable();
if (ret != W25Q64_OK)
{
return ret;
}
ret = w25q64_command_addr(W25Q64_CMD_BLOCK_ERASE_64K, addr);
if (ret != W25Q64_OK)
{
return ret;
}
w25q64_cs_high();
return w25q64_wait_ready(2000U);
}
W25Q64_Status_t W25Q64_ChipErase(void)
{
uint8_t cmd = W25Q64_CMD_CHIP_ERASE;
if (!w25q64_ready)
{
return W25Q64_ERR_NOT_ATTACHED;
}
if (w25q64_write_enable() != W25Q64_OK)
{
return W25Q64_ERR_HAL;
}
w25q64_cs_low();
if (w25q64_spi_tx(&cmd, 1U) != W25Q64_OK)
{
w25q64_cs_high();
return W25Q64_ERR_HAL;
}
w25q64_cs_high();
return w25q64_wait_ready(100000U);
}

View File

@@ -0,0 +1,59 @@
#ifndef W25Q64_H
#define W25Q64_H
#include "main.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define W25Q64_PAGE_SIZE 256U
#define W25Q64_SECTOR_SIZE 4096U
#define W25Q64_FLASH_SIZE 0x800000U
#define W25Q64_JEDEC_ID 0xEF4017U
#define W25Q64_CMD_WRITE_ENABLE 0x06U
#define W25Q64_CMD_READ_STATUS_1 0x05U
#define W25Q64_CMD_READ_ID 0x9FU
#define W25Q64_CMD_READ_DATA 0x03U
#define W25Q64_CMD_PAGE_PROGRAM 0x02U
#define W25Q64_CMD_SECTOR_ERASE 0x20U
#define W25Q64_CMD_BLOCK_ERASE_64K 0xD8U
#define W25Q64_CMD_CHIP_ERASE 0xC7U
typedef enum
{
W25Q64_OK = 0,
W25Q64_ERR_PARAM = -1,
W25Q64_ERR_NOT_ATTACHED = -2,
W25Q64_ERR_HAL = -3,
W25Q64_ERR_ID = -4,
W25Q64_ERR_BUSY = -5
} W25Q64_Status_t;
typedef struct
{
SPI_HandleTypeDef *hspi;
GPIO_TypeDef *cs_port;
uint16_t cs_pin;
uint32_t timeout_ms;
} W25Q64_Handle_t;
void W25Q64_Attach(W25Q64_Handle_t *handle);
void W25Q64_Detach(void);
W25Q64_Status_t W25Q64_Init(void);
W25Q64_Status_t W25Q64_ReadID(uint32_t *jedec_id);
W25Q64_Status_t W25Q64_Read(uint32_t addr, uint8_t *data, uint32_t len);
W25Q64_Status_t W25Q64_Write(uint32_t addr, const uint8_t *data, uint32_t len);
W25Q64_Status_t W25Q64_EraseSector(uint32_t addr);
W25Q64_Status_t W25Q64_Erase64K(uint32_t addr);
W25Q64_Status_t W25Q64_ChipErase(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -16,9 +16,74 @@
#include "ws2812.h"
#define WS2812_SPI_TIMEOUT 2U
#define WS2812_RESET_BYTES 100U
#define WS2812_LowLevel 0xC0 // 0码
#define WS2812_HighLevel 0xF0 // 1码
static SPI_HandleTypeDef ws2812_spi;
static uint8_t ws2812_spi_initialized = 0U;
static uint8_t WS2812_SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
if (ws2812_spi_initialized != 0U)
{
return 1U;
}
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI6;
PeriphClkInitStruct.Spi6ClockSelection = RCC_SPI6CLKSOURCE_HSE;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
return 0U;
}
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_SPI6_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF8_SPI6;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
ws2812_spi.Instance = SPI6;
ws2812_spi.Init.Mode = SPI_MODE_MASTER;
ws2812_spi.Init.Direction = SPI_DIRECTION_2LINES_TXONLY;
ws2812_spi.Init.DataSize = SPI_DATASIZE_8BIT;
ws2812_spi.Init.CLKPolarity = SPI_POLARITY_LOW;
ws2812_spi.Init.CLKPhase = SPI_PHASE_2EDGE;
ws2812_spi.Init.NSS = SPI_NSS_SOFT;
ws2812_spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
ws2812_spi.Init.FirstBit = SPI_FIRSTBIT_MSB;
ws2812_spi.Init.TIMode = SPI_TIMODE_DISABLE;
ws2812_spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
ws2812_spi.Init.CRCPolynomial = 0x0;
ws2812_spi.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
ws2812_spi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
ws2812_spi.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
ws2812_spi.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
ws2812_spi.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
ws2812_spi.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
ws2812_spi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
ws2812_spi.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
ws2812_spi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
ws2812_spi.Init.IOSwap = SPI_IO_SWAP_DISABLE;
if (HAL_SPI_Init(&ws2812_spi) != HAL_OK)
{
return 0U;
}
ws2812_spi_initialized = 1U;
return 1U;
}
/**
* @brief
* @param [in] r My Param doc
@@ -29,17 +94,28 @@ void WS2812_Ctrl(uint8_t r, uint8_t g, uint8_t b)
{
uint8_t txbuf[24];
uint8_t res = 0;
for (auto i = 0; i < 8; i++)
if (WS2812_SPI_Init() == 0U)
{
return;
}
for (uint8_t i = 0; i < 8U; i++)
{
txbuf[7 - i] = (((g >> i) & 0x01) ? WS2812_HighLevel : WS2812_LowLevel) >> 1;
txbuf[15 - i] = (((r >> i) & 0x01) ? WS2812_HighLevel : WS2812_LowLevel) >> 1;
txbuf[23 - i] = (((b >> i) & 0x01) ? WS2812_HighLevel : WS2812_LowLevel) >> 1;
}
HAL_SPI_Transmit(&WS2812_SPI_UNIT, &res, 0, 0xFFFF);
while (WS2812_SPI_UNIT.State != HAL_SPI_STATE_READY);
HAL_SPI_Transmit(&WS2812_SPI_UNIT, txbuf, 24, 0xFFFF);
for (auto i = 0; i < 100; i++)
if (HAL_SPI_Transmit(&ws2812_spi, txbuf, sizeof(txbuf), WS2812_SPI_TIMEOUT) != HAL_OK)
{
HAL_SPI_Transmit(&WS2812_SPI_UNIT, &res, 1, 0xFFFF);
return;
}
for (uint8_t i = 0; i < WS2812_RESET_BYTES; i++)
{
if (HAL_SPI_Transmit(&ws2812_spi, &res, 1U, WS2812_SPI_TIMEOUT) != HAL_OK)
{
return;
}
}
}

View File

@@ -3,8 +3,5 @@
#include "main.h"
#define WS2812_SPI_UNIT hspi6
extern SPI_HandleTypeDef WS2812_SPI_UNIT;
void WS2812_Ctrl(uint8_t r, uint8_t g, uint8_t b);
#endif

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
MIT licence
Copyright (c) 2025 LVGL Kft
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
/**
* @file lv_version.h
* The current version of LVGL
*/
#ifndef LV_VERSION_H
#define LV_VERSION_H
#define LVGL_VERSION_MAJOR 9
#define LVGL_VERSION_MINOR 5
#define LVGL_VERSION_PATCH 0
#define LVGL_VERSION_INFO ""
#endif /* LV_VERSION_H */

View File

@@ -0,0 +1,209 @@
/**
* @file lvgl.h
* Include all LVGL related headers
*/
#ifndef LVGL_H
#define LVGL_H
#ifdef __cplusplus
extern "C" {
#endif
/***************************
* CURRENT VERSION OF LVGL
***************************/
#include "lv_version.h"
/*********************
* INCLUDES
*********************/
#include "src/lv_init.h"
#include "src/stdlib/lv_mem.h"
#include "src/stdlib/lv_string.h"
#include "src/stdlib/lv_sprintf.h"
#include "src/misc/lv_log.h"
#include "src/misc/lv_timer.h"
#include "src/misc/lv_math.h"
#include "src/misc/lv_array.h"
#include "src/misc/lv_async.h"
#include "src/misc/lv_anim_timeline.h"
#include "src/misc/lv_profiler_builtin.h"
#include "src/misc/lv_rb.h"
#include "src/misc/lv_utils.h"
#include "src/misc/lv_iter.h"
#include "src/misc/lv_circle_buf.h"
#include "src/misc/lv_tree.h"
#include "src/osal/lv_os.h"
#include "src/tick/lv_tick.h"
#include "src/core/lv_obj.h"
#include "src/core/lv_group.h"
#include "src/core/lv_refr.h"
#include "src/core/lv_observer.h"
#include "src/indev/lv_indev.h"
#include "src/indev/lv_indev_gesture.h"
#include "src/indev/lv_gridnav.h"
#include "src/display/lv_display.h"
#include "src/font/lv_font.h"
#include "src/font/binfont_loader/lv_binfont_loader.h"
#include "src/font/fmt_txt/lv_font_fmt_txt.h"
#include "src/font/imgfont/lv_imgfont.h"
#include "src/font/font_manager/lv_font_manager.h"
#include "src/widgets/animimage/lv_animimage.h"
#include "src/widgets/arc/lv_arc.h"
#include "src/widgets/arclabel/lv_arclabel.h"
#include "src/widgets/bar/lv_bar.h"
#include "src/widgets/button/lv_button.h"
#include "src/widgets/buttonmatrix/lv_buttonmatrix.h"
#include "src/widgets/calendar/lv_calendar.h"
#include "src/widgets/canvas/lv_canvas.h"
#include "src/widgets/chart/lv_chart.h"
#include "src/widgets/checkbox/lv_checkbox.h"
#include "src/widgets/dropdown/lv_dropdown.h"
#include "src/widgets/gif/lv_gif.h"
#include "src/widgets/image/lv_image.h"
#include "src/widgets/imagebutton/lv_imagebutton.h"
#include "src/widgets/keyboard/lv_keyboard.h"
#include "src/widgets/label/lv_label.h"
#include "src/widgets/led/lv_led.h"
#include "src/widgets/line/lv_line.h"
#include "src/widgets/list/lv_list.h"
#include "src/widgets/lottie/lv_lottie.h"
#include "src/widgets/menu/lv_menu.h"
#include "src/widgets/msgbox/lv_msgbox.h"
#include "src/widgets/roller/lv_roller.h"
#include "src/widgets/scale/lv_scale.h"
#include "src/widgets/slider/lv_slider.h"
#include "src/widgets/span/lv_span.h"
#include "src/widgets/spinbox/lv_spinbox.h"
#include "src/widgets/spinner/lv_spinner.h"
#include "src/widgets/switch/lv_switch.h"
#include "src/widgets/table/lv_table.h"
#include "src/widgets/tabview/lv_tabview.h"
#include "src/widgets/textarea/lv_textarea.h"
#include "src/widgets/tileview/lv_tileview.h"
#include "src/widgets/win/lv_win.h"
#include "src/widgets/3dtexture/lv_3dtexture.h"
#include "src/widgets/ime/lv_ime_pinyin.h"
#include "src/debugging/sysmon/lv_sysmon.h"
#include "src/debugging/monkey/lv_monkey.h"
#include "src/debugging/test/lv_test.h"
#include "src/others/fragment/lv_fragment.h"
#include "src/others/file_explorer/lv_file_explorer.h"
#include "src/others/translation/lv_translation.h"
#include "src/libs/barcode/lv_barcode.h"
#include "src/libs/bin_decoder/lv_bin_decoder.h"
#include "src/libs/bmp/lv_bmp.h"
#include "src/libs/rle/lv_rle.h"
#include "src/libs/fsdrv/lv_fsdrv.h"
#include "src/libs/lodepng/lv_lodepng.h"
#include "src/libs/qrcode/lv_qrcode.h"
#include "src/libs/freetype/lv_freetype.h"
#include "src/libs/rlottie/lv_rlottie.h"
#include "src/libs/tiny_ttf/lv_tiny_ttf.h"
#include "src/layouts/lv_layout.h"
#include "src/draw/lv_draw_buf.h"
#include "src/draw/lv_draw_vector.h"
#include "src/draw/sw/lv_draw_sw_utils.h"
#include "src/draw/eve/lv_draw_eve_target.h"
#include "src/draw/snapshot/lv_snapshot.h"
#include "src/themes/lv_theme.h"
#include "src/drivers/lv_drivers.h"
/* Define LV_DISABLE_API_MAPPING using a compiler option
* to make sure your application is not using deprecated names */
#ifndef LV_DISABLE_API_MAPPING
#include "src/lv_api_map_v8.h"
#include "src/lv_api_map_v9_0.h"
#include "src/lv_api_map_v9_1.h"
#include "src/lv_api_map_v9_2.h"
#include "src/lv_api_map_v9_3.h"
#include "src/lv_api_map_v9_4.h"
#endif /*LV_DISABLE_API_MAPPING*/
#if LV_USE_PRIVATE_API
#include "src/lvgl_private.h"
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
/** Gives 1 if the x.y.z version is supported in the current version
* Usage:
*
* - Require v6
* #if LV_VERSION_CHECK(6,0,0)
* new_func_in_v6();
* #endif
*
*
* - Require at least v5.3
* #if LV_VERSION_CHECK(5,3,0)
* new_feature_from_v5_3();
* #endif
*
*
* - Require v5.3.2 bugfixes
* #if LV_VERSION_CHECK(5,3,2)
* bugfix_in_v5_3_2();
* #endif
*
*/
#define LV_VERSION_CHECK(x,y,z) (x == LVGL_VERSION_MAJOR && (y < LVGL_VERSION_MINOR || (y == LVGL_VERSION_MINOR && z <= LVGL_VERSION_PATCH)))
/**
* Wrapper functions for VERSION macros
*/
static inline int lv_version_major(void)
{
return LVGL_VERSION_MAJOR;
}
static inline int lv_version_minor(void)
{
return LVGL_VERSION_MINOR;
}
static inline int lv_version_patch(void)
{
return LVGL_VERSION_PATCH;
}
static inline const char * lv_version_info(void)
{
return LVGL_VERSION_INFO;
}
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LVGL_H*/

View File

@@ -0,0 +1,131 @@
/**
* @file lvgl_private.h
*
*/
#ifndef LVGL_PRIVATE_H
#define LVGL_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "src/core/lv_global.h"
#include "src/display/lv_display_private.h"
#include "src/indev/lv_indev_private.h"
#include "src/misc/lv_text_private.h"
#include "src/misc/cache/lv_cache.h"
#include "src/misc/cache/lv_cache_entry_private.h"
#include "src/misc/cache/lv_cache_private.h"
#include "src/layouts/lv_layout_private.h"
#include "src/stdlib/lv_mem_private.h"
#include "src/others/file_explorer/lv_file_explorer_private.h"
#include "src/others/fragment/lv_fragment_private.h"
#include "src/others/translation/lv_translation_private.h"
#include "src/libs/qrcode/lv_qrcode_private.h"
#include "src/libs/barcode/lv_barcode_private.h"
#include "src/draw/lv_draw_triangle_private.h"
#include "src/draw/lv_draw_private.h"
#include "src/draw/lv_draw_rect_private.h"
#include "src/draw/lv_draw_image_private.h"
#include "src/draw/lv_image_decoder_private.h"
#include "src/draw/lv_draw_label_private.h"
#include "src/draw/lv_draw_vector_private.h"
#include "src/draw/lv_draw_buf_private.h"
#include "src/draw/sw/lv_draw_sw_private.h"
#include "src/draw/sw/lv_draw_sw_mask_private.h"
#include "src/draw/sw/blend/lv_draw_sw_blend_private.h"
#include "src/drivers/libinput/lv_xkb_private.h"
#include "src/drivers/libinput/lv_libinput_private.h"
#include "src/drivers/evdev/lv_evdev_private.h"
#include "src/themes/lv_theme_private.h"
#include "src/core/lv_refr_private.h"
#include "src/core/lv_obj_style_private.h"
#include "src/core/lv_obj_private.h"
#include "src/core/lv_obj_scroll_private.h"
#include "src/core/lv_obj_draw_private.h"
#include "src/core/lv_obj_class_private.h"
#include "src/core/lv_group_private.h"
#include "src/core/lv_obj_event_private.h"
#include "src/core/lv_observer_private.h"
#include "src/debugging/sysmon/lv_sysmon_private.h"
#include "src/debugging/monkey/lv_monkey_private.h"
#include "src/font/fmt_txt/lv_font_fmt_txt_private.h"
#include "src/misc/lv_timer_private.h"
#include "src/misc/lv_area_private.h"
#include "src/misc/lv_fs_private.h"
#include "src/misc/lv_profiler_builtin_private.h"
#include "src/misc/lv_event_private.h"
#include "src/misc/lv_bidi_private.h"
#include "src/misc/lv_rb_private.h"
#include "src/misc/lv_style_private.h"
#include "src/misc/lv_color_op_private.h"
#include "src/misc/lv_anim_private.h"
#include "src/misc/lv_anim_timeline_private.h"
#include "src/widgets/msgbox/lv_msgbox_private.h"
#include "src/widgets/buttonmatrix/lv_buttonmatrix_private.h"
#include "src/widgets/slider/lv_slider_private.h"
#include "src/widgets/switch/lv_switch_private.h"
#include "src/widgets/calendar/lv_calendar_private.h"
#include "src/widgets/imagebutton/lv_imagebutton_private.h"
#include "src/widgets/bar/lv_bar_private.h"
#include "src/widgets/image/lv_image_private.h"
#include "src/widgets/textarea/lv_textarea_private.h"
#include "src/widgets/table/lv_table_private.h"
#include "src/widgets/checkbox/lv_checkbox_private.h"
#include "src/widgets/roller/lv_roller_private.h"
#include "src/widgets/win/lv_win_private.h"
#include "src/widgets/keyboard/lv_keyboard_private.h"
#include "src/widgets/line/lv_line_private.h"
#include "src/widgets/animimage/lv_animimage_private.h"
#include "src/widgets/dropdown/lv_dropdown_private.h"
#include "src/widgets/menu/lv_menu_private.h"
#include "src/widgets/chart/lv_chart_private.h"
#include "src/widgets/button/lv_button_private.h"
#include "src/widgets/scale/lv_scale_private.h"
#include "src/widgets/led/lv_led_private.h"
#include "src/widgets/arc/lv_arc_private.h"
#include "src/widgets/tileview/lv_tileview_private.h"
#include "src/widgets/spinbox/lv_spinbox_private.h"
#include "src/widgets/span/lv_span_private.h"
#include "src/widgets/label/lv_label_private.h"
#include "src/widgets/canvas/lv_canvas_private.h"
#include "src/widgets/tabview/lv_tabview_private.h"
#include "src/widgets/3dtexture/lv_3dtexture_private.h"
#include "src/widgets/ime/lv_ime_pinyin_private.h"
#include "src/tick/lv_tick_private.h"
#include "src/stdlib/builtin/lv_tlsf_private.h"
#include "src/libs/rlottie/lv_rlottie_private.h"
#include "src/widgets/lottie/lv_lottie_private.h"
#include "src/osal/lv_os_private.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LVGL_PRIVATE_H*/

View File

@@ -0,0 +1,312 @@
/**
* @file lv_global.h
*
*/
#ifndef LV_GLOBAL_H
#define LV_GLOBAL_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../lv_conf_internal.h"
#include "../misc/lv_types.h"
#include "../draw/lv_draw.h"
#if LV_USE_DRAW_SW
#include "../draw/sw/lv_draw_sw.h"
#endif
#include "../misc/lv_anim.h"
#include "../misc/lv_area.h"
#include "../misc/lv_color_op.h"
#include "../misc/lv_ll.h"
#include "../misc/lv_log.h"
#include "../misc/lv_style.h"
#include "../misc/lv_timer.h"
#include "../osal/lv_os_private.h"
#include "../debugging/sysmon/lv_sysmon.h"
#include "../stdlib/builtin/lv_tlsf.h"
#if LV_USE_FONT_COMPRESSED
#include "../font/fmt_txt/lv_font_fmt_txt_private.h"
#endif
#include "../tick/lv_tick.h"
#include "../layouts/lv_layout.h"
#include "../misc/lv_types.h"
#include "../misc/lv_timer_private.h"
#include "../misc/lv_anim_private.h"
#include "../tick/lv_tick_private.h"
#include "../draw/lv_draw_buf_private.h"
#include "../draw/lv_draw_private.h"
#include "../draw/sw/lv_draw_sw_private.h"
#include "../draw/sw/lv_draw_sw_mask_private.h"
#include "../stdlib/builtin/lv_tlsf_private.h"
#include "../debugging/sysmon/lv_sysmon_private.h"
#include "../debugging/test/lv_test_private.h"
#include "../layouts/lv_layout_private.h"
/*********************
* DEFINES
*********************/
#define ZERO_MEM_SENTINEL 0xa1b2c3d4
/**********************
* TYPEDEFS
**********************/
#if LV_USE_SPAN != 0
struct _snippet_stack;
#endif
#if LV_USE_FREETYPE
struct _lv_freetype_context_t;
#endif
#if LV_USE_PROFILER && LV_USE_PROFILER_BUILTIN
struct _lv_profiler_builtin_ctx_t;
#endif
#if LV_USE_NUTTX
struct _lv_nuttx_ctx_t;
#endif
typedef struct _lv_global_t {
/**
* User data for the LVGL library. Move from the bottom of the struct
* to avoid breaking the ABI. E.g., if the user data is used by a
* closed-source library, this can help to avoid re-compiling the library
* when the lvgl-related configs are changed.
*/
void * user_data;
bool inited;
bool deinit_in_progress; /**< Can be used e.g. in the LV_EVENT_DELETE to deinit the drivers too */
lv_ll_t disp_ll;
lv_display_t * disp_refresh;
lv_display_t * disp_default;
lv_ll_t style_trans_ll;
bool style_refresh;
uint32_t style_custom_table_size;
uint32_t style_last_custom_prop_id;
uint8_t * style_custom_prop_flag_lookup_table;
lv_ll_t group_ll;
lv_group_t * group_default;
lv_ll_t indev_ll;
lv_indev_t * indev_active;
lv_obj_t * indev_obj_active;
uint32_t layout_count;
lv_layout_dsc_t * layout_list;
bool layout_update_mutex;
uint32_t memory_zero;
uint32_t math_rand_seed;
lv_event_t * event_header;
uint32_t event_last_register_id;
lv_timer_state_t timer_state;
lv_anim_state_t anim_state;
lv_tick_state_t tick_state;
lv_draw_buf_handlers_t draw_buf_handlers;
lv_draw_buf_handlers_t font_draw_buf_handlers;
lv_draw_buf_handlers_t image_cache_draw_buf_handlers; /**< Ensure that all assigned draw buffers
* can be managed by image cache. */
lv_ll_t img_decoder_ll;
#if LV_USE_OS != LV_OS_NONE
lv_mutex_t img_decoder_info_lock;
lv_mutex_t img_decoder_open_lock;
#endif
lv_cache_t * img_cache;
lv_cache_t * img_header_cache;
lv_draw_global_info_t draw_info;
lv_ll_t draw_sw_blend_handler_ll;
#if defined(LV_DRAW_SW_SHADOW_CACHE_SIZE) && LV_DRAW_SW_SHADOW_CACHE_SIZE > 0
lv_draw_sw_shadow_cache_t sw_shadow_cache;
#endif
#if LV_DRAW_SW_COMPLEX
lv_draw_sw_mask_radius_circle_dsc_arr_t sw_circle_cache;
#endif
#if LV_USE_LOG
lv_log_print_g_cb_t custom_log_print_cb;
#endif
#if LV_USE_LOG && LV_LOG_USE_TIMESTAMP
uint32_t log_last_log_time;
#endif
#if LV_USE_THEME_SIMPLE
void * theme_simple;
#endif
#if LV_USE_THEME_DEFAULT
void * theme_default;
#endif
#if LV_USE_THEME_MONO
void * theme_mono;
#endif
#if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN
lv_tlsf_state_t tlsf_state;
#endif
lv_ll_t fsdrv_ll;
#if LV_USE_FS_STDIO != '\0'
lv_fs_drv_t stdio_fs_drv;
#endif
#if LV_USE_FS_POSIX
lv_fs_drv_t posix_fs_drv;
#endif
#if LV_USE_FS_FATFS
lv_fs_drv_t fatfs_fs_drv;
#endif
#if LV_USE_FS_WIN32 != '\0'
lv_fs_drv_t win32_fs_drv;
#endif
#if LV_USE_FS_UEFI
lv_fs_drv_t uefi_fs_drv;
#endif
#if LV_USE_FS_LITTLEFS
lv_fs_drv_t littlefs_fs_drv;
#endif
#if LV_USE_FS_ARDUINO_ESP_LITTLEFS
lv_fs_drv_t arduino_esp_littlefs_fs_drv;
#endif
#if LV_USE_FS_ARDUINO_SD
lv_fs_drv_t arduino_sd_fs_drv;
#endif
#if LV_USE_FS_FROGFS
lv_fs_drv_t frogfs_fs_drv;
#endif
#if LV_USE_FREETYPE
struct _lv_freetype_context_t * ft_context;
#endif
#if LV_USE_FONT_COMPRESSED
lv_font_fmt_rle_t font_fmt_rle;
#endif
#if LV_USE_SPAN != 0
struct _snippet_stack * span_snippet_stack;
#endif
#if LV_USE_PROFILER && LV_USE_PROFILER_BUILTIN
struct _lv_profiler_builtin_ctx_t * profiler_context;
#endif
#if LV_USE_FILE_EXPLORER
lv_style_t file_explorer_quick_access_style;
size_t file_explorer_count;
#endif
#if LV_USE_MEM_MONITOR
lv_sysmon_backend_data_t sysmon_mem;
#endif
#if LV_USE_IME_PINYIN != 0
size_t ime_cand_len;
#endif
#if LV_USE_OBJ_ID_BUILTIN
void * objid_array;
uint32_t objid_count;
#endif
#if LV_USE_TEST
lv_test_state_t test_state;
#endif
#if LV_USE_TRANSLATION
lv_ll_t translation_packs_ll;
const char * translation_selected_lang;
#endif
#if LV_USE_NUTTX
struct _lv_nuttx_ctx_t * nuttx_ctx;
#endif
#if LV_USE_OS != LV_OS_NONE
lv_mutex_t lv_general_mutex;
#endif
#if defined(__linux__)
lv_linux_proc_stat_t linux_last_proc_stat;
#if LV_SYSMON_PROC_IDLE_AVAILABLE
uint64_t linux_last_self_proc_time_ticks;
lv_linux_proc_stat_t linux_last_system_total_ticks_stat;
#endif
#endif
#if LV_USE_OS == LV_OS_FREERTOS
uint32_t freertos_idle_time_sum;
uint32_t freertos_non_idle_time_sum;
uint32_t freertos_task_switch_timestamp;
bool freertos_idle_task_running;
#endif
#if LV_USE_EVDEV
lv_evdev_discovery_t * evdev_discovery;
#endif
#if LV_USE_DRAW_EVE
lv_draw_eve_unit_t * draw_eve_unit;
#endif
} lv_global_t;
/**********************
* MACROS
**********************/
#if LV_ENABLE_GLOBAL_CUSTOM
#include LV_GLOBAL_CUSTOM_INCLUDE
#ifndef LV_GLOBAL_CUSTOM
#define LV_GLOBAL_CUSTOM() lv_global_default()
#endif
#define LV_GLOBAL_DEFAULT() LV_GLOBAL_CUSTOM()
#else
LV_ATTRIBUTE_EXTERN_DATA extern lv_global_t lv_global;
#define LV_GLOBAL_DEFAULT() (&lv_global)
#endif
/**********************
* GLOBAL PROTOTYPES
**********************/
#if LV_ENABLE_GLOBAL_CUSTOM
/**
* Get the default global object for current thread
* @return pointer to the default global object
*/
lv_global_t * lv_global_default(void);
#endif
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GLOBAL_H*/

View File

@@ -0,0 +1,566 @@
/**
* @file lv_group.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_group_private.h"
#include "../core/lv_obj_private.h"
#include "../core/lv_global.h"
#include "../indev/lv_indev.h"
#include "../misc/lv_types.h"
/*********************
* DEFINES
*********************/
#define default_group LV_GLOBAL_DEFAULT()->group_default
#define group_ll_p &(LV_GLOBAL_DEFAULT()->group_ll)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static bool focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
void * (*move)(const lv_ll_t *, const void *));
static void lv_group_refocus(lv_group_t * g);
static lv_indev_t * get_indev(const lv_group_t * g);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_group_init(void)
{
lv_ll_init(group_ll_p, sizeof(lv_group_t));
}
void lv_group_deinit(void)
{
lv_ll_clear(group_ll_p);
}
lv_group_t * lv_group_create(void)
{
lv_group_t * group = lv_ll_ins_head(group_ll_p);
LV_ASSERT_MALLOC(group);
if(group == NULL) return NULL;
lv_ll_init(&group->obj_ll, sizeof(lv_obj_t *));
group->obj_focus = NULL;
group->frozen = 0;
group->focus_cb = NULL;
group->edge_cb = NULL;
group->editing = 0;
group->refocus_policy = LV_GROUP_REFOCUS_POLICY_PREV;
group->wrap = 1;
group->user_data = NULL;
#if LV_USE_EXT_DATA
group->ext_data.free_cb = NULL;
group->ext_data.data = NULL;
#endif
return group;
}
void lv_group_delete(lv_group_t * group)
{
/*Defocus the currently focused object*/
LV_ASSERT_NULL(group);
if(group->obj_focus != NULL) {
lv_obj_send_event(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group));
lv_obj_invalidate(*group->obj_focus);
}
/*Remove the objects from the group*/
lv_obj_t ** obj;
LV_LL_READ(&group->obj_ll, obj) {
if((*obj)->spec_attr)(*obj)->spec_attr->group_p = NULL;
}
/*Remove the group from any indev devices */
lv_indev_t * indev = lv_indev_get_next(NULL);
while(indev) {
if(lv_indev_get_group(indev) == group) {
lv_indev_set_group(indev, NULL);
}
indev = lv_indev_get_next(indev);
}
/*If the group is the default group, set the default group as NULL*/
if(group == lv_group_get_default()) lv_group_set_default(NULL);
lv_ll_clear(&(group->obj_ll));
lv_ll_remove(group_ll_p, group);
#if LV_USE_EXT_DATA
if(group->ext_data.free_cb) {
group->ext_data.free_cb(group->ext_data.data);
group->ext_data.data = NULL;
}
#endif
lv_free(group);
}
void lv_group_set_default(lv_group_t * group)
{
default_group = group;
}
lv_group_t * lv_group_get_default(void)
{
return default_group;
}
void lv_group_add_obj(lv_group_t * group, lv_obj_t * obj)
{
if(group == NULL) return;
LV_LOG_TRACE("begin");
/*Be sure the object is removed from its current group*/
lv_group_remove_obj(obj);
if(obj->spec_attr == NULL) lv_obj_allocate_spec_attr(obj);
obj->spec_attr->group_p = group;
lv_obj_t ** next = lv_ll_ins_tail(&group->obj_ll);
LV_ASSERT_MALLOC(next);
if(next == NULL) return;
*next = obj;
/*If the head and the tail is equal then there is only one object in the linked list.
*In this case automatically activate it*/
if(lv_ll_get_head(&group->obj_ll) == next) {
lv_group_refocus(group);
}
LV_LOG_TRACE("finished");
}
void lv_group_swap_obj(lv_obj_t * obj1, lv_obj_t * obj2)
{
lv_group_t * g1 = lv_obj_get_group(obj1);
lv_group_t * g2 = lv_obj_get_group(obj2);
if(g1 != g2) return;
if(g1 == NULL) return;
/*Do not add the object twice*/
lv_obj_t ** obj_i;
LV_LL_READ(&g1->obj_ll, obj_i) {
if((*obj_i) == obj1)(*obj_i) = obj2;
else if((*obj_i) == obj2)(*obj_i) = obj1;
}
lv_obj_t * focused = lv_group_get_focused(g1);
if(focused == obj1) lv_group_focus_obj(obj2);
else if(focused == obj2) lv_group_focus_obj(obj1);
}
void lv_group_remove_obj(lv_obj_t * obj)
{
lv_group_t * g = lv_obj_get_group(obj);
if(g == NULL) return;
LV_LOG_TRACE("begin");
/*Focus on the next object*/
if(g->obj_focus && *g->obj_focus == obj) {
if(g->frozen) g->frozen = 0;
/*If this is the only object in the group then focus to nothing.*/
if(lv_ll_get_head(&g->obj_ll) == g->obj_focus && lv_ll_get_tail(&g->obj_ll) == g->obj_focus) {
lv_obj_send_event(*g->obj_focus, LV_EVENT_DEFOCUSED, get_indev(g));
}
/*If there more objects in the group then focus to the next/prev object*/
else {
lv_group_refocus(g);
}
}
/*If the focuses object is still the same then it was the only object in the group but it will
*be deleted. Set the `obj_focus` to NULL to get back to the initial state of the group with
*zero objects*/
if(g->obj_focus && *g->obj_focus == obj) {
g->obj_focus = NULL;
}
/*Search the object and remove it from its group*/
lv_obj_t ** i;
LV_LL_READ(&g->obj_ll, i) {
if(*i == obj) {
lv_ll_remove(&g->obj_ll, i);
lv_free(i);
if(obj->spec_attr) obj->spec_attr->group_p = NULL;
break;
}
}
LV_LOG_TRACE("finished");
}
void lv_group_remove_all_objs(lv_group_t * group)
{
LV_ASSERT_NULL(group);
/*Defocus the currently focused object*/
if(group->obj_focus != NULL) {
lv_obj_send_event(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group));
lv_obj_invalidate(*group->obj_focus);
group->obj_focus = NULL;
}
/*Remove the objects from the group*/
lv_obj_t ** obj;
LV_LL_READ(&group->obj_ll, obj) {
if((*obj)->spec_attr)(*obj)->spec_attr->group_p = NULL;
}
lv_ll_clear(&(group->obj_ll));
}
void lv_group_focus_obj(lv_obj_t * obj)
{
if(obj == NULL) return;
lv_group_t * g = lv_obj_get_group(obj);
if(g == NULL) return;
if(g->frozen != 0) return;
/*On defocus edit mode must be leaved*/
lv_group_set_editing(g, false);
lv_obj_t ** i;
LV_LL_READ(&g->obj_ll, i) {
if(*i == obj) {
if(g->obj_focus != NULL && obj != *g->obj_focus) { /*Do not defocus if the same object needs to be focused again*/
lv_result_t res = lv_obj_send_event(*g->obj_focus, LV_EVENT_DEFOCUSED, get_indev(g));
if(res != LV_RESULT_OK) return;
lv_obj_invalidate(*g->obj_focus);
}
g->obj_focus = i;
if(g->obj_focus != NULL) {
if(g->focus_cb) g->focus_cb(g);
lv_result_t res = lv_obj_send_event(*g->obj_focus, LV_EVENT_FOCUSED, get_indev(g));
if(res != LV_RESULT_OK) return;
lv_obj_invalidate(*g->obj_focus);
}
break;
}
}
}
void lv_group_focus_next(lv_group_t * group)
{
LV_ASSERT_NULL(group);
bool focus_changed = focus_next_core(group, lv_ll_get_head, lv_ll_get_next);
if(group->edge_cb) {
if(!focus_changed)
group->edge_cb(group, true);
}
}
void lv_group_focus_prev(lv_group_t * group)
{
LV_ASSERT_NULL(group);
bool focus_changed = focus_next_core(group, lv_ll_get_tail, lv_ll_get_prev);
if(group->edge_cb) {
if(!focus_changed)
group->edge_cb(group, false);
}
}
void lv_group_focus_freeze(lv_group_t * group, bool en)
{
LV_ASSERT_NULL(group);
if(en == false) group->frozen = 0;
else group->frozen = 1;
}
lv_result_t lv_group_send_data(lv_group_t * group, uint32_t c)
{
LV_ASSERT_NULL(group);
lv_obj_t * act = lv_group_get_focused(group);
if(act == NULL) return LV_RESULT_OK;
if(lv_obj_has_state(act, LV_STATE_DISABLED)) return LV_RESULT_OK;
return lv_obj_send_event(act, LV_EVENT_KEY, &c);
}
void lv_group_set_focus_cb(lv_group_t * group, lv_group_focus_cb_t focus_cb)
{
if(group == NULL) return;
group->focus_cb = focus_cb;
}
void lv_group_set_edge_cb(lv_group_t * group, lv_group_edge_cb_t edge_cb)
{
LV_ASSERT_NULL(group);
group->edge_cb = edge_cb;
}
void lv_group_set_editing(lv_group_t * group, bool edit)
{
LV_ASSERT_NULL(group);
uint8_t en_val = edit ? 1 : 0;
if(en_val == group->editing) return; /*Do not set the same mode again*/
group->editing = en_val;
lv_obj_t * focused = lv_group_get_focused(group);
if(focused) {
lv_result_t res = lv_obj_send_event(*group->obj_focus, LV_EVENT_FOCUSED, get_indev(group));
if(res != LV_RESULT_OK) return;
lv_obj_invalidate(focused);
}
}
void lv_group_set_refocus_policy(lv_group_t * group, lv_group_refocus_policy_t policy)
{
LV_ASSERT_NULL(group);
group->refocus_policy = policy & 0x01;
}
void lv_group_set_wrap(lv_group_t * group, bool en)
{
LV_ASSERT_NULL(group);
group->wrap = en ? 1 : 0;
}
lv_obj_t * lv_group_get_focused(const lv_group_t * group)
{
if(!group) return NULL;
if(group->obj_focus == NULL) return NULL;
return *group->obj_focus;
}
lv_group_focus_cb_t lv_group_get_focus_cb(const lv_group_t * group)
{
if(!group) return NULL;
return group->focus_cb;
}
lv_group_edge_cb_t lv_group_get_edge_cb(const lv_group_t * group)
{
if(!group) return NULL;
return group->edge_cb;
}
bool lv_group_get_editing(const lv_group_t * group)
{
if(!group) return false;
return group->editing;
}
bool lv_group_get_wrap(lv_group_t * group)
{
if(!group) return false;
return group->wrap;
}
uint32_t lv_group_get_obj_count(lv_group_t * group)
{
LV_ASSERT_NULL(group);
return lv_ll_get_len(&group->obj_ll);
}
lv_obj_t * lv_group_get_obj_by_index(lv_group_t * group, uint32_t index)
{
uint32_t len = 0;
lv_obj_t ** obj;
LV_LL_READ(&group->obj_ll, obj) {
if(len == index) {
return *obj;
}
len++;
}
return NULL;
}
uint32_t lv_group_get_count(void)
{
return lv_ll_get_len(group_ll_p);
}
lv_group_t * lv_group_by_index(uint32_t index)
{
uint32_t len = 0;
lv_group_t * group;
LV_LL_READ_BACK(group_ll_p, group) {
if(len == index) {
return group;
}
len++;
}
return NULL;
}
#if LV_USE_EXT_DATA
void lv_group_set_external_data(lv_group_t * group, void * data, void (* free_cb)(void * data))
{
if(!group) {
LV_LOG_WARN("Can't attach external user data and destructor callback to a NULL group");
return;
}
group->ext_data.data = data;
group->ext_data.free_cb = free_cb;
}
#endif
void lv_group_set_user_data(lv_group_t * group, void * user_data)
{
if(group == NULL) return;
group->user_data = user_data;
}
void * lv_group_get_user_data(const lv_group_t * group)
{
if(group == NULL) return NULL;
return group->user_data;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void lv_group_refocus(lv_group_t * g)
{
/*Refocus must temporarily allow wrapping to work correctly*/
uint8_t temp_wrap = g->wrap;
g->wrap = 1;
if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_NEXT)
lv_group_focus_next(g);
else if(g->refocus_policy == LV_GROUP_REFOCUS_POLICY_PREV)
lv_group_focus_prev(g);
/*Restore wrap property*/
g->wrap = temp_wrap;
}
static bool focus_next_core(lv_group_t * group, void * (*begin)(const lv_ll_t *),
void * (*move)(const lv_ll_t *, const void *))
{
bool focus_changed = false;
if(group->frozen) return focus_changed;
lv_obj_t ** obj_next = group->obj_focus;
lv_obj_t ** obj_sentinel = NULL;
bool can_move = true;
bool can_begin = true;
for(;;) {
if(obj_next == NULL) {
if(group->wrap || obj_sentinel == NULL) {
if(!can_begin) return focus_changed;
obj_next = begin(&group->obj_ll);
can_move = false;
can_begin = false;
}
else {
/*Currently focused object is the last/first in the group, keep it that way*/
return focus_changed;
}
}
if(obj_sentinel == NULL) {
obj_sentinel = obj_next;
if(obj_sentinel == NULL) return focus_changed; /*Group is empty*/
}
if(can_move) {
obj_next = move(&group->obj_ll, obj_next);
/*Give up if we walked the entire list and haven't found another visible object*/
if(obj_next == obj_sentinel) return focus_changed;
}
can_move = true;
if(obj_next == NULL) continue;
if(lv_obj_get_state(*obj_next) & LV_STATE_DISABLED) continue;
/*Hidden objects don't receive focus.
*If any parent is hidden, the object is also hidden)*/
lv_obj_t * parent = *obj_next;
while(parent) {
if(lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) break;
parent = lv_obj_get_parent(parent);
}
if(parent && lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) continue;
/*If we got her a good candidate is found*/
break;
}
if(obj_next == group->obj_focus) return focus_changed; /*There's only one visible object and it's already focused*/
if(group->obj_focus) {
lv_result_t res = lv_obj_send_event(*group->obj_focus, LV_EVENT_DEFOCUSED, get_indev(group));
if(res != LV_RESULT_OK) return focus_changed;
lv_obj_invalidate(*group->obj_focus);
}
group->obj_focus = obj_next;
lv_result_t res = lv_obj_send_event(*group->obj_focus, LV_EVENT_FOCUSED, get_indev(group));
if(res != LV_RESULT_OK) return focus_changed;
lv_obj_invalidate(*group->obj_focus);
if(group->focus_cb) group->focus_cb(group);
focus_changed = true;
return focus_changed;
}
/**
* Find an indev preferably with POINTER type (because it's the most generic) that uses the given group.
* In other words, find an indev, that is related to the given group.
* In the worst case simply return the latest indev
* @param g a group the find in the indevs
* @return the suggested indev
*/
static lv_indev_t * get_indev(const lv_group_t * g)
{
lv_indev_t * indev_guess = NULL;
lv_indev_t * indev = lv_indev_get_next(NULL);
while(indev) {
lv_indev_type_t indev_type = lv_indev_get_type(indev);
/*Prefer POINTER*/
if(indev_type == LV_INDEV_TYPE_POINTER) return indev;
if(lv_indev_get_group(indev) == g) {
indev_guess = indev;
}
indev = lv_indev_get_next(indev);
}
return indev_guess;
}

View File

@@ -0,0 +1,278 @@
/**
* @file lv_group.h
*
*/
#ifndef LV_GROUP_H
#define LV_GROUP_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../lv_conf_internal.h"
#include "../misc/lv_types.h"
#include "../misc/lv_ll.h"
/*********************
* DEFINES
*********************/
/** Predefined keys to control which Widget has focus via lv_group_send(group, c) */
typedef enum {
LV_KEY_UP = 17, /*0x11*/
LV_KEY_DOWN = 18, /*0x12*/
LV_KEY_RIGHT = 19, /*0x13*/
LV_KEY_LEFT = 20, /*0x14*/
LV_KEY_ESC = 27, /*0x1B*/
LV_KEY_DEL = 127, /*0x7F*/
LV_KEY_BACKSPACE = 8, /*0x08*/
LV_KEY_ENTER = 10, /*0x0A, '\n'*/
LV_KEY_NEXT = 9, /*0x09, '\t'*/
LV_KEY_PREV = 11, /*0x0B, '*/
LV_KEY_HOME = 2, /*0x02, STX*/
LV_KEY_END = 3, /*0x03, ETX*/
} lv_key_t;
/**********************
* TYPEDEFS
**********************/
typedef void (*lv_group_focus_cb_t)(lv_group_t *);
typedef void (*lv_group_edge_cb_t)(lv_group_t *, bool);
typedef enum {
LV_GROUP_REFOCUS_POLICY_NEXT = 0,
LV_GROUP_REFOCUS_POLICY_PREV = 1
} lv_group_refocus_policy_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create new Widget group.
* @return pointer to the new Widget group
*/
lv_group_t * lv_group_create(void);
/**
* Delete group object.
* @param group pointer to a group
*/
void lv_group_delete(lv_group_t * group);
/**
* Set default group. New Widgets will be added to this group if it's enabled in
* their class with `add_to_def_group = true`.
* @param group pointer to a group (can be `NULL`)
*/
void lv_group_set_default(lv_group_t * group);
/**
* Get default group.
* @return pointer to the default group
*/
lv_group_t * lv_group_get_default(void);
/**
* Add an Widget to group.
* @param group pointer to a group
* @param obj pointer to a Widget to add
*/
void lv_group_add_obj(lv_group_t * group, lv_obj_t * obj);
/**
* Swap 2 Widgets in group. Widgets must be in the same group.
* @param obj1 pointer to a Widget
* @param obj2 pointer to another Widget
*/
void lv_group_swap_obj(lv_obj_t * obj1, lv_obj_t * obj2);
/**
* Remove a Widget from its group.
* @param obj pointer to Widget to remove
*/
void lv_group_remove_obj(lv_obj_t * obj);
/**
* Remove all Widgets from a group.
* @param group pointer to a group
*/
void lv_group_remove_all_objs(lv_group_t * group);
/**
* Focus on a Widget (defocus the current).
* @param obj pointer to Widget to focus on
*/
void lv_group_focus_obj(lv_obj_t * obj);
/**
* Focus on next Widget in a group (defocus the current).
* @param group pointer to a group
*/
void lv_group_focus_next(lv_group_t * group);
/**
* Focus on previous Widget in a group (defocus the current).
* @param group pointer to a group
*/
void lv_group_focus_prev(lv_group_t * group);
/**
* Do not allow changing focus from current Widget.
* @param group pointer to a group
* @param en true: freeze, false: release freezing (normal mode)
*/
void lv_group_focus_freeze(lv_group_t * group, bool en);
/**
* Send a control character to Widget that has focus in a group.
* @param group pointer to a group
* @param c a character (use LV_KEY_.. to navigate)
* @return result of Widget with focus in group.
*/
lv_result_t lv_group_send_data(lv_group_t * group, uint32_t c);
/**
* Set a function for a group which will be called when a new Widget has focus.
* @param group pointer to a group
* @param focus_cb the call back function or NULL if unused
*/
void lv_group_set_focus_cb(lv_group_t * group, lv_group_focus_cb_t focus_cb);
/**
* Set a function for a group which will be called when a focus edge is reached
* @param group pointer to a group
* @param edge_cb the call back function or NULL if unused
*/
void lv_group_set_edge_cb(lv_group_t * group, lv_group_edge_cb_t edge_cb);
/**
* Set whether the next or previous Widget in a group gets focus when Widget that has
* focus is deleted.
* @param group pointer to a group
* @param policy new refocus policy enum
*/
void lv_group_set_refocus_policy(lv_group_t * group, lv_group_refocus_policy_t policy);
/**
* Manually set the current mode (edit or navigate).
* @param group pointer to group
* @param edit true: edit mode; false: navigate mode
*/
void lv_group_set_editing(lv_group_t * group, bool edit);
/**
* Set whether moving focus to next/previous Widget will allow wrapping from
* first->last or last->first Widget.
* @param group pointer to group
* @param en true: wrapping enabled; false: wrapping disabled
*/
void lv_group_set_wrap(lv_group_t * group, bool en);
/**
* Get Widget that has focus, or NULL if there isn't one.
* @param group pointer to a group
* @return pointer to Widget with focus
*/
lv_obj_t * lv_group_get_focused(const lv_group_t * group);
/**
* Get focus callback function of a group.
* @param group pointer to a group
* @return the call back function or NULL if not set
*/
lv_group_focus_cb_t lv_group_get_focus_cb(const lv_group_t * group);
/**
* Get edge callback function of a group.
* @param group pointer to a group
* @return the call back function or NULL if not set
*/
lv_group_edge_cb_t lv_group_get_edge_cb(const lv_group_t * group);
/**
* Get current mode (edit or navigate).
* @param group pointer to group
* @return true: edit mode; false: navigate mode
*/
bool lv_group_get_editing(const lv_group_t * group);
/**
* Get whether moving focus to next/previous Widget will allow wrapping from
* first->last or last->first Widget.
* @param group pointer to group
*/
bool lv_group_get_wrap(lv_group_t * group);
/**
* Get number of Widgets in group.
* @param group pointer to a group
* @return number of Widgets in the group
*/
uint32_t lv_group_get_obj_count(lv_group_t * group);
/**
* Get nth Widget within group.
* @param group pointer to a group
* @param index index of Widget within the group
* @return pointer to Widget
*/
lv_obj_t * lv_group_get_obj_by_index(lv_group_t * group, uint32_t index);
/**
* Get the number of groups.
* @return number of groups
*/
uint32_t lv_group_get_count(void);
/**
* Get a group by its index.
* @param index index of the group
* @return pointer to the group
*/
lv_group_t * lv_group_by_index(uint32_t index);
#if LV_USE_EXT_DATA
/**
* @brief Attaches external user data and destructor callback to a group
*
* Associates custom user data with an LVGL group and specifies a destructor function
* that will be automatically invoked when the group is deleted to properly clean up
* the associated resources.
*
* @param group Pointer to a group
* @param data User-defined data pointer to associate with a group
* @param free_cb Callback function for cleaning up ext_data when group is deleted.
* Receives ext_data as parameter. NULL means no cleanup required.
*/
void lv_group_set_external_data(lv_group_t * group, void * data, void (* free_cb)(void * data));
#endif
/**
* Set user data to the group
* @param group pointer to a group
* @param user_data pointer to user data
*/
void lv_group_set_user_data(lv_group_t * group, void * user_data);
/**
* Get a pointer to the user data of the group
* @param indev pointer to a group
* @return pointer to the user data or NULL if group is NULL
*/
void * lv_group_get_user_data(const lv_group_t * group);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GROUP_H*/

View File

@@ -0,0 +1,78 @@
/**
* @file lv_group_private.h
*
*/
#ifndef LV_GROUP_PRIVATE_H
#define LV_GROUP_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../misc/lv_ext_data.h"
#include "lv_group.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**
* Groups can be used to logically hold objects so that they can be individually focused.
* They are NOT for laying out objects on a screen (try layouts for that).
*/
struct _lv_group_t {
#if LV_USE_EXT_DATA
lv_ext_data_t ext_data;
#endif
lv_ll_t obj_ll; /**< Linked list to store the objects in the group*/
lv_obj_t ** obj_focus; /**< The object in focus*/
lv_group_focus_cb_t focus_cb; /**< A function to call when a new object is focused (optional)*/
lv_group_edge_cb_t edge_cb; /**< A function to call when an edge is reached, no more focus
targets are available in this direction (to allow edge feedback
like a sound or a scroll bounce) */
void * user_data;
uint8_t frozen : 1; /**< 1: can't focus to new object*/
uint8_t editing : 1; /**< 1: Edit mode, 0: Navigate mode*/
uint8_t refocus_policy : 1; /**< 1: Focus prev if focused on deletion. 0: Focus next if focused on
deletion.*/
uint8_t wrap : 1; /**< 1: Focus next/prev can wrap at end of list. 0: Focus next/prev stops at end
of list.*/
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Init the group module
* @remarks Internal function, do not call directly.
*/
void lv_group_init(void);
/**
* Deinit the group module
* @remarks Internal function, do not call directly.
*/
void lv_group_deinit(void);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GROUP_PRIVATE_H*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,501 @@
/**
* @file lv_obj.h
*
*/
#ifndef LV_OBJ_H
#define LV_OBJ_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../lv_conf_internal.h"
#include "../misc/lv_types.h"
#include "../misc/lv_style.h"
#include "../misc/lv_area.h"
#include "../misc/lv_color.h"
#include "../misc/lv_assert.h"
#include "lv_obj_tree.h"
#include "lv_obj_pos.h"
#include "lv_obj_scroll.h"
#include "lv_obj_style.h"
#include "lv_obj_draw.h"
#include "lv_obj_class.h"
#include "lv_obj_event.h"
#include "lv_obj_property.h"
#include "lv_group.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**
* On/Off features controlling the object's behavior.
* OR-ed values are possible
*
* Note: update obj flags corresponding properties below
* whenever add/remove flags or change bit definition of flags.
*/
typedef enum {
LV_OBJ_FLAG_HIDDEN = (1u << 0), /**< Make the object hidden. (Like it wasn't there at all)*/
LV_OBJ_FLAG_CLICKABLE = (1u << 1), /**< Make the object clickable by the input devices*/
LV_OBJ_FLAG_CLICK_FOCUSABLE = (1u << 2), /**< Add focused state to the object when clicked*/
LV_OBJ_FLAG_CHECKABLE = (1u << 3), /**< Toggle checked state when the object is clicked*/
LV_OBJ_FLAG_SCROLLABLE = (1u << 4), /**< Make the object scrollable*/
LV_OBJ_FLAG_SCROLL_ELASTIC = (1u << 5), /**< Allow scrolling inside but with slower speed*/
LV_OBJ_FLAG_SCROLL_MOMENTUM = (1u << 6), /**< Make the object scroll further when "thrown"*/
LV_OBJ_FLAG_SCROLL_ONE = (1u << 7), /**< Allow scrolling only one snappable children*/
LV_OBJ_FLAG_SCROLL_CHAIN_HOR = (1u << 8), /**< Allow propagating the horizontal scroll to a parent*/
LV_OBJ_FLAG_SCROLL_CHAIN_VER = (1u << 9), /**< Allow propagating the vertical scroll to a parent*/
LV_OBJ_FLAG_SCROLL_CHAIN = (LV_OBJ_FLAG_SCROLL_CHAIN_HOR | LV_OBJ_FLAG_SCROLL_CHAIN_VER),
LV_OBJ_FLAG_SCROLL_ON_FOCUS = (1u << 10), /**< Automatically scroll object to make it visible when focused*/
LV_OBJ_FLAG_SCROLL_WITH_ARROW = (1u << 11), /**< Allow scrolling the focused object with arrow keys*/
LV_OBJ_FLAG_SNAPPABLE = (1u << 12), /**< If scroll snap is enabled on the parent it can snap to this object*/
LV_OBJ_FLAG_PRESS_LOCK = (1u << 13), /**< Keep the object pressed even if the press slid from the object*/
LV_OBJ_FLAG_EVENT_BUBBLE = (1u << 14), /**< Propagate the events to the parent too*/
LV_OBJ_FLAG_GESTURE_BUBBLE = (1u << 15), /**< Propagate the gestures to the parent*/
LV_OBJ_FLAG_ADV_HITTEST = (1u << 16), /**< Allow performing more accurate hit (click) test. E.g. consider rounded corners.*/
LV_OBJ_FLAG_IGNORE_LAYOUT = (1u << 17), /**< Make the object not positioned by the layouts*/
LV_OBJ_FLAG_FLOATING = (1u << 18), /**< Do not scroll the object when the parent scrolls and ignore layout*/
LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS = (1u << 19), /**< Send `LV_EVENT_DRAW_TASK_ADDED` events*/
LV_OBJ_FLAG_OVERFLOW_VISIBLE = (1u << 20),/**< Do not clip the children to the parent's ext draw size*/
LV_OBJ_FLAG_EVENT_TRICKLE = (1u << 21), /**< Propagate the events to the children too*/
LV_OBJ_FLAG_STATE_TRICKLE = (1u << 22), /**< Propagate the states to the children too*/
LV_OBJ_FLAG_LAYOUT_1 = (1u << 23), /**< Custom flag, free to use by layouts*/
LV_OBJ_FLAG_LAYOUT_2 = (1u << 24), /**< Custom flag, free to use by layouts*/
#if LV_USE_FLEX
LV_OBJ_FLAG_FLEX_IN_NEW_TRACK = LV_OBJ_FLAG_LAYOUT_1, /**< Start a new flex track on this item*/
#endif
LV_OBJ_FLAG_WIDGET_1 = (1u << 25), /**< Custom flag, free to use by widget*/
LV_OBJ_FLAG_WIDGET_2 = (1u << 26), /**< Custom flag, free to use by widget*/
LV_OBJ_FLAG_USER_1 = (1u << 27), /**< Custom flag, free to use by user*/
LV_OBJ_FLAG_USER_2 = (1u << 28), /**< Custom flag, free to use by user*/
LV_OBJ_FLAG_USER_3 = (1u << 29), /**< Custom flag, free to use by user*/
LV_OBJ_FLAG_USER_4 = (1u << 30), /**< Custom flag, free to use by user*/
} lv_obj_flag_t;
#if LV_USE_OBJ_PROPERTY
enum _lv_signed_prop_id_t {
/*OBJ flag properties */
LV_PROPERTY_ID(OBJ, FLAG_START, LV_PROPERTY_TYPE_INT, 0),
LV_PROPERTY_ID(OBJ, FLAG_HIDDEN, LV_PROPERTY_TYPE_INT, 0),
LV_PROPERTY_ID(OBJ, FLAG_CLICKABLE, LV_PROPERTY_TYPE_INT, 1),
LV_PROPERTY_ID(OBJ, FLAG_CLICK_FOCUSABLE, LV_PROPERTY_TYPE_INT, 2),
LV_PROPERTY_ID(OBJ, FLAG_CHECKABLE, LV_PROPERTY_TYPE_INT, 3),
LV_PROPERTY_ID(OBJ, FLAG_SCROLLABLE, LV_PROPERTY_TYPE_INT, 4),
LV_PROPERTY_ID(OBJ, FLAG_SCROLL_ELASTIC, LV_PROPERTY_TYPE_INT, 5),
LV_PROPERTY_ID(OBJ, FLAG_SCROLL_MOMENTUM, LV_PROPERTY_TYPE_INT, 6),
LV_PROPERTY_ID(OBJ, FLAG_SCROLL_ONE, LV_PROPERTY_TYPE_INT, 7),
LV_PROPERTY_ID(OBJ, FLAG_SCROLL_CHAIN_HOR, LV_PROPERTY_TYPE_INT, 8),
LV_PROPERTY_ID(OBJ, FLAG_SCROLL_CHAIN_VER, LV_PROPERTY_TYPE_INT, 9),
LV_PROPERTY_ID(OBJ, FLAG_SCROLL_ON_FOCUS, LV_PROPERTY_TYPE_INT, 10),
LV_PROPERTY_ID(OBJ, FLAG_SCROLL_WITH_ARROW, LV_PROPERTY_TYPE_INT, 11),
LV_PROPERTY_ID(OBJ, FLAG_SNAPPABLE, LV_PROPERTY_TYPE_INT, 12),
LV_PROPERTY_ID(OBJ, FLAG_PRESS_LOCK, LV_PROPERTY_TYPE_INT, 13),
LV_PROPERTY_ID(OBJ, FLAG_EVENT_BUBBLE, LV_PROPERTY_TYPE_INT, 14),
LV_PROPERTY_ID(OBJ, FLAG_GESTURE_BUBBLE, LV_PROPERTY_TYPE_INT, 15),
LV_PROPERTY_ID(OBJ, FLAG_ADV_HITTEST, LV_PROPERTY_TYPE_INT, 16),
LV_PROPERTY_ID(OBJ, FLAG_IGNORE_LAYOUT, LV_PROPERTY_TYPE_INT, 17),
LV_PROPERTY_ID(OBJ, FLAG_FLOATING, LV_PROPERTY_TYPE_INT, 18),
LV_PROPERTY_ID(OBJ, FLAG_SEND_DRAW_TASK_EVENTS, LV_PROPERTY_TYPE_INT, 19),
LV_PROPERTY_ID(OBJ, FLAG_OVERFLOW_VISIBLE, LV_PROPERTY_TYPE_INT, 20),
LV_PROPERTY_ID(OBJ, FLAG_EVENT_TRICKLE, LV_PROPERTY_TYPE_INT, 21),
LV_PROPERTY_ID(OBJ, FLAG_STATE_TRICKLE, LV_PROPERTY_TYPE_INT, 22),
LV_PROPERTY_ID(OBJ, FLAG_LAYOUT_1, LV_PROPERTY_TYPE_INT, 23),
LV_PROPERTY_ID(OBJ, FLAG_LAYOUT_2, LV_PROPERTY_TYPE_INT, 24),
LV_PROPERTY_ID(OBJ, FLAG_FLEX_IN_NEW_TRACK, LV_PROPERTY_TYPE_INT, 23), /*Mapped to FLAG_LAYOUT_1*/
LV_PROPERTY_ID(OBJ, FLAG_WIDGET_1, LV_PROPERTY_TYPE_INT, 25),
LV_PROPERTY_ID(OBJ, FLAG_WIDGET_2, LV_PROPERTY_TYPE_INT, 26),
LV_PROPERTY_ID(OBJ, FLAG_USER_1, LV_PROPERTY_TYPE_INT, 27),
LV_PROPERTY_ID(OBJ, FLAG_USER_2, LV_PROPERTY_TYPE_INT, 28),
LV_PROPERTY_ID(OBJ, FLAG_USER_3, LV_PROPERTY_TYPE_INT, 29),
LV_PROPERTY_ID(OBJ, FLAG_USER_4, LV_PROPERTY_TYPE_INT, 30),
LV_PROPERTY_ID(OBJ, FLAG_END, LV_PROPERTY_TYPE_INT, 30),
LV_PROPERTY_ID(OBJ, STATE_START, LV_PROPERTY_TYPE_INT, 31),
LV_PROPERTY_ID(OBJ, STATE_ALT, LV_PROPERTY_TYPE_INT, 31),
/*1 reserved*/
LV_PROPERTY_ID(OBJ, STATE_CHECKED, LV_PROPERTY_TYPE_INT, 33),
LV_PROPERTY_ID(OBJ, STATE_FOCUSED, LV_PROPERTY_TYPE_INT, 34),
LV_PROPERTY_ID(OBJ, STATE_FOCUS_KEY, LV_PROPERTY_TYPE_INT, 35),
LV_PROPERTY_ID(OBJ, STATE_EDITED, LV_PROPERTY_TYPE_INT, 36),
LV_PROPERTY_ID(OBJ, STATE_HOVERED, LV_PROPERTY_TYPE_INT, 37),
LV_PROPERTY_ID(OBJ, STATE_PRESSED, LV_PROPERTY_TYPE_INT, 38),
LV_PROPERTY_ID(OBJ, STATE_SCROLLED, LV_PROPERTY_TYPE_INT, 39),
LV_PROPERTY_ID(OBJ, STATE_DISABLED, LV_PROPERTY_TYPE_INT, 40),
/*2 reserved*/
LV_PROPERTY_ID(OBJ, STATE_USER_1, LV_PROPERTY_TYPE_INT, 43),
LV_PROPERTY_ID(OBJ, STATE_USER_2, LV_PROPERTY_TYPE_INT, 44),
LV_PROPERTY_ID(OBJ, STATE_USER_3, LV_PROPERTY_TYPE_INT, 45),
LV_PROPERTY_ID(OBJ, STATE_USER_4, LV_PROPERTY_TYPE_INT, 46),
LV_PROPERTY_ID(OBJ, STATE_ANY, LV_PROPERTY_TYPE_INT, 47),
LV_PROPERTY_ID(OBJ, STATE_END, LV_PROPERTY_TYPE_INT, 47),
/*OBJ normal properties*/
LV_PROPERTY_ID(OBJ, PARENT, LV_PROPERTY_TYPE_OBJ, 48),
LV_PROPERTY_ID(OBJ, X, LV_PROPERTY_TYPE_INT, 49),
LV_PROPERTY_ID(OBJ, Y, LV_PROPERTY_TYPE_INT, 50),
LV_PROPERTY_ID(OBJ, W, LV_PROPERTY_TYPE_INT, 51),
LV_PROPERTY_ID(OBJ, H, LV_PROPERTY_TYPE_INT, 52),
LV_PROPERTY_ID(OBJ, CONTENT_WIDTH, LV_PROPERTY_TYPE_INT, 53),
LV_PROPERTY_ID(OBJ, CONTENT_HEIGHT, LV_PROPERTY_TYPE_INT, 54),
LV_PROPERTY_ID(OBJ, LAYOUT, LV_PROPERTY_TYPE_INT, 55),
LV_PROPERTY_ID(OBJ, ALIGN, LV_PROPERTY_TYPE_INT, 56),
LV_PROPERTY_ID(OBJ, SCROLLBAR_MODE, LV_PROPERTY_TYPE_INT, 57),
LV_PROPERTY_ID(OBJ, SCROLL_DIR, LV_PROPERTY_TYPE_INT, 58),
LV_PROPERTY_ID(OBJ, SCROLL_SNAP_X, LV_PROPERTY_TYPE_INT, 59),
LV_PROPERTY_ID(OBJ, SCROLL_SNAP_Y, LV_PROPERTY_TYPE_INT, 60),
LV_PROPERTY_ID(OBJ, SCROLL_X, LV_PROPERTY_TYPE_INT, 61),
LV_PROPERTY_ID(OBJ, SCROLL_Y, LV_PROPERTY_TYPE_INT, 62),
LV_PROPERTY_ID(OBJ, SCROLL_TOP, LV_PROPERTY_TYPE_INT, 63),
LV_PROPERTY_ID(OBJ, SCROLL_BOTTOM, LV_PROPERTY_TYPE_INT, 64),
LV_PROPERTY_ID(OBJ, SCROLL_LEFT, LV_PROPERTY_TYPE_INT, 65),
LV_PROPERTY_ID(OBJ, SCROLL_RIGHT, LV_PROPERTY_TYPE_INT, 66),
LV_PROPERTY_ID(OBJ, SCROLL_END, LV_PROPERTY_TYPE_POINT, 67),
LV_PROPERTY_ID(OBJ, EXT_DRAW_SIZE, LV_PROPERTY_TYPE_INT, 68),
LV_PROPERTY_ID(OBJ, EVENT_COUNT, LV_PROPERTY_TYPE_INT, 69),
LV_PROPERTY_ID(OBJ, SCREEN, LV_PROPERTY_TYPE_OBJ, 70),
LV_PROPERTY_ID(OBJ, DISPLAY, LV_PROPERTY_TYPE_POINTER, 71),
LV_PROPERTY_ID(OBJ, CHILD_COUNT, LV_PROPERTY_TYPE_INT, 72),
LV_PROPERTY_ID(OBJ, INDEX, LV_PROPERTY_TYPE_INT, 73),
LV_PROPERTY_OBJ_END,
};
#endif
/**
* Make the base object's class publicly available.
*/
LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_obj_class;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create a base object (a rectangle)
* @param parent pointer to a parent object. If NULL then a screen will be created.
* @return pointer to the new object
*/
lv_obj_t * lv_obj_create(lv_obj_t * parent);
/*=====================
* Setter functions
*====================*/
/**
* Set one or more flags
* @param obj pointer to an object
* @param f OR-ed values from `lv_obj_flag_t` to set.
*/
void lv_obj_add_flag(lv_obj_t * obj, lv_obj_flag_t f);
/**
* Remove one or more flags
* @param obj pointer to an object
* @param f OR-ed values from `lv_obj_flag_t` to clear.
*/
void lv_obj_remove_flag(lv_obj_t * obj, lv_obj_flag_t f);
/**
* Set add or remove one or more flags.
* @param obj pointer to an object
* @param f OR-ed values from `lv_obj_flag_t` to update.
* @param v true: add the flags; false: remove the flags
*/
void lv_obj_set_flag(lv_obj_t * obj, lv_obj_flag_t f, bool v);
/**
* Add one or more states to the object. The other state bits will remain unchanged.
* If specified in the styles, transition animation will be started from the previous state to the current.
* @param obj pointer to an object
* @param state the states to add. E.g `LV_STATE_PRESSED | LV_STATE_FOCUSED`
*/
void lv_obj_add_state(lv_obj_t * obj, lv_state_t state);
/**
* Remove one or more states to the object. The other state bits will remain unchanged.
* If specified in the styles, transition animation will be started from the previous state to the current.
* @param obj pointer to an object
* @param state the states to add. E.g `LV_STATE_PRESSED | LV_STATE_FOCUSED`
*/
void lv_obj_remove_state(lv_obj_t * obj, lv_state_t state);
/**
* Add or remove one or more states to the object. The other state bits will remain unchanged.
* @param obj pointer to an object
* @param state the states to add. E.g `LV_STATE_PRESSED | LV_STATE_FOCUSED`
* @param v true: add the states; false: remove the states
*/
void lv_obj_set_state(lv_obj_t * obj, lv_state_t state, bool v);
/**
* Set the user_data field of the object
* @param obj pointer to an object
* @param user_data pointer to the new user_data.
*/
void lv_obj_set_user_data(lv_obj_t * obj, void * user_data);
/** Allow only one RADIO_BUTTON sibling to be checked
* @param obj pointer to a widget
* @param en enable or disable radio button behavior
*/
void lv_obj_set_radio_button(lv_obj_t * obj, bool en);
/*=======================
* Getter functions
*======================*/
/**
* Check if a given flag or all the given flags are set on an object.
* @param obj pointer to an object
* @param f the flag(s) to check (OR-ed values can be used)
* @return true: all flags are set; false: not all flags are set
*/
bool lv_obj_has_flag(const lv_obj_t * obj, lv_obj_flag_t f);
/**
* Check if a given flag or any of the flags are set on an object.
* @param obj pointer to an object
* @param f the flag(s) to check (OR-ed values can be used)
* @return true: at least one flag is set; false: none of the flags are set
*/
bool lv_obj_has_flag_any(const lv_obj_t * obj, lv_obj_flag_t f);
/**
* Get the state of an object
* @param obj pointer to an object
* @return the state (OR-ed values from `lv_state_t`)
*/
lv_state_t lv_obj_get_state(const lv_obj_t * obj);
/**
* Check if the object is in a given state or not.
* @param obj pointer to an object
* @param state a state or combination of states to check
* @return true: `obj` is in `state`; false: `obj` is not in `state`
*/
bool lv_obj_has_state(const lv_obj_t * obj, lv_state_t state);
/** Get whether the object is a radio button
* @param obj pointer to a widget
* @return true if radio button behavior is enabled
*/
bool lv_obj_is_radio_button(const lv_obj_t * obj);
/**
* Get the group of the object
* @param obj pointer to an object
* @return the pointer to group of the object
*/
lv_group_t * lv_obj_get_group(const lv_obj_t * obj);
/**
* Get the user_data field of the object
* @param obj pointer to an object
* @return the pointer to the user_data of the object
*/
void * lv_obj_get_user_data(lv_obj_t * obj);
/*=======================
* Other functions
*======================*/
/**
* Allocate special data for an object if not allocated yet.
* @param obj pointer to an object
*/
void lv_obj_allocate_spec_attr(lv_obj_t * obj);
/**
* Check the type of obj.
* @param obj pointer to an object
* @param class_p a class to check (e.g. `lv_slider_class`)
* @return true: `class_p` is the `obj` class.
*/
bool lv_obj_check_type(const lv_obj_t * obj, const lv_obj_class_t * class_p);
/**
* Check if any object has a given class (type).
* It checks the ancestor classes too.
* @param obj pointer to an object
* @param class_p a class to check (e.g. `lv_slider_class`)
* @return true: `obj` has the given class
*/
bool lv_obj_has_class(const lv_obj_t * obj, const lv_obj_class_t * class_p);
/**
* Get the class (type) of the object
* @param obj pointer to an object
* @return the class (type) of the object
*/
const lv_obj_class_t * lv_obj_get_class(const lv_obj_t * obj);
/**
* Check if any object is still "alive".
* @param obj pointer to an object
* @return true: valid
*/
bool lv_obj_is_valid(const lv_obj_t * obj);
/**
* Utility to set an object reference to NULL when it gets deleted.
* The reference should be in a location that will not become invalid
* during the object's lifetime, i.e. static or allocated.
* @param obj_ptr a pointer to a pointer to an object
*/
void lv_obj_null_on_delete(lv_obj_t ** obj_ptr);
/**
* Add an event handler to a widget that will load a screen on a trigger.
* @param obj pointer to widget which should load the screen
* @param trigger an event code, e.g. `LV_EVENT_CLICKED`
* @param screen the screen to load (must be a valid widget)
* @param anim_type element of `lv_screen_load_anim_t` the screen load animation
* @param duration duration of the animation in milliseconds
* @param delay delay before the screen load in milliseconds
*/
void lv_obj_add_screen_load_event(lv_obj_t * obj, lv_event_code_t trigger, lv_obj_t * screen,
lv_screen_load_anim_t anim_type, uint32_t duration, uint32_t delay);
/**
* Add an event handler to a widget that will create a screen on a trigger.
* The created screen will be deleted when it's unloaded
* @param obj pointer to widget which should load the screen
* @param trigger an event code, e.g. `LV_EVENT_CLICKED`
* @param screen_create_cb a callback to create the screen, e.g. `lv_obj_t * myscreen_create(void)`
* @param anim_type element of `lv_screen_load_anim_t` the screen load animation
* @param duration duration of the animation in milliseconds
* @param delay delay before the screen load in milliseconds
*/
void lv_obj_add_screen_create_event(lv_obj_t * obj, lv_event_code_t trigger, lv_screen_create_cb_t screen_create_cb,
lv_screen_load_anim_t anim_type, uint32_t duration, uint32_t delay);
/**
* Play a timeline animation on a trigger
* @param obj pointer to widget which should trigger playing the animation
* @param trigger an event code, e.g. `LV_EVENT_CLICKED`
* @param at pointer to an animation timeline
* @param delay wait time before starting the animation
* @param reverse true: play in reverse
*/
void lv_obj_add_play_timeline_event(lv_obj_t * obj, lv_event_code_t trigger, lv_anim_timeline_t * at, uint32_t delay,
bool reverse);
#if LV_USE_OBJ_ID
/**
* Set an id for an object.
* @param obj pointer to an object
* @param id the id of the object
*/
void lv_obj_set_id(lv_obj_t * obj, void * id);
/**
* Get the id of an object.
* @param obj pointer to an object
* @return the id of the object
*/
void * lv_obj_get_id(const lv_obj_t * obj);
/**
* DEPRECATED IDs are used only to print the widget trees.
* To find a widget use `lv_obj_find_by_name`
*
* Get the child object by its id.
* It will check children and grandchildren recursively.
* Function `lv_obj_id_compare` is used to matched obj id with given id.
*
* @param obj pointer to an object
* @param id the id of the child object
* @return pointer to the child object or NULL if not found
*/
lv_obj_t * lv_obj_find_by_id(const lv_obj_t * obj, const void * id);
/**
* Assign id to object if not previously assigned.
* This function gets called automatically when LV_OBJ_ID_AUTO_ASSIGN is enabled.
*
* Set `LV_USE_OBJ_ID_BUILTIN` to use the builtin method to generate object ID.
* Otherwise, these functions including `lv_obj_[set|assign|free|stringify]_id` and
* `lv_obj_id_compare`should be implemented externally.
*
* @param class_p the class this obj belongs to. Note obj->class_p is the class currently being constructed.
* @param obj pointer to an object
*/
void lv_obj_assign_id(const lv_obj_class_t * class_p, lv_obj_t * obj);
/**
* Free resources allocated by `lv_obj_assign_id` or `lv_obj_set_id`.
* This function is also called automatically when object is deleted.
* @param obj pointer to an object
*/
void lv_obj_free_id(lv_obj_t * obj);
/**
* Compare two obj id, return 0 if they are equal.
*
* Set `LV_USE_OBJ_ID_BUILTIN` to use the builtin method for compare.
* Otherwise, it must be implemented externally.
*
* @param id1: the first id
* @param id2: the second id
* @return 0 if they are equal, non-zero otherwise.
*/
int lv_obj_id_compare(const void * id1, const void * id2);
/**
* Format an object's id into a string.
* @param obj pointer to an object
* @param buf buffer to write the string into
* @param len length of the buffer
*/
const char * lv_obj_stringify_id(lv_obj_t * obj, char * buf, uint32_t len);
#if LV_USE_OBJ_ID_BUILTIN
/**
* Free resources used by builtin ID generator.
*/
void lv_objid_builtin_destroy(void);
#endif
#endif /*LV_USE_OBJ_ID*/
/**********************
* MACROS
**********************/
#if LV_USE_ASSERT_OBJ
# define LV_ASSERT_OBJ(obj_p, obj_class) \
do { \
LV_ASSERT_MSG(obj_p != NULL, "The object is NULL"); \
LV_ASSERT_MSG(lv_obj_has_class(obj_p, obj_class) == true, "Incompatible object type."); \
LV_ASSERT_MSG(lv_obj_is_valid(obj_p) == true, "The object is invalid, deleted or corrupted?"); \
} while(0)
# else
# define LV_ASSERT_OBJ(obj_p, obj_class) LV_ASSERT_NULL(obj_p)
#endif
#if LV_USE_LOG && LV_LOG_TRACE_OBJ_CREATE
# define LV_TRACE_OBJ_CREATE(...) LV_LOG_TRACE(__VA_ARGS__)
#else
# define LV_TRACE_OBJ_CREATE(...)
#endif
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_OBJ_H*/

View File

@@ -0,0 +1,233 @@
/**
* @file lv_obj_class.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_obj_class_private.h"
#include "lv_obj_private.h"
#include "../themes/lv_theme.h"
#include "../display/lv_display.h"
#include "../display/lv_display_private.h"
#include "../stdlib/lv_string.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS (&lv_obj_class)
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_obj_construct(const lv_obj_class_t * class_p, lv_obj_t * obj);
static uint32_t get_instance_size(const lv_obj_class_t * class_p);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_obj_t * lv_obj_class_create_obj(const lv_obj_class_t * class_p, lv_obj_t * parent)
{
LV_TRACE_OBJ_CREATE("Creating object with %p class on %p parent", (void *)class_p, (void *)parent);
uint32_t s = get_instance_size(class_p);
lv_obj_t * obj = lv_malloc_zeroed(s);
if(obj == NULL) return NULL;
obj->class_p = class_p;
obj->parent = parent;
/*Create a screen*/
if(parent == NULL) {
LV_TRACE_OBJ_CREATE("creating a screen");
lv_display_t * disp = lv_display_get_default();
if(!disp) {
LV_LOG_WARN("No display created yet. No place to assign the new screen");
lv_free(obj);
return NULL;
}
if(disp->screens == NULL) {
disp->screen_cnt = 0;
}
lv_obj_t ** screens = lv_realloc(disp->screens, sizeof(lv_obj_t *) * (disp->screen_cnt + 1));
LV_ASSERT_MALLOC(screens);
if(screens == NULL) {
lv_free(obj);
return NULL;
}
disp->screen_cnt++;
disp->screens = screens;
disp->screens[disp->screen_cnt - 1] = obj;
/*Set coordinates to full screen size*/
obj->coords.x1 = 0;
obj->coords.y1 = 0;
obj->coords.x2 = lv_display_get_horizontal_resolution(NULL) - 1;
obj->coords.y2 = lv_display_get_vertical_resolution(NULL) - 1;
}
/*Create a normal object*/
else {
LV_TRACE_OBJ_CREATE("creating normal object");
LV_ASSERT_OBJ(parent, MY_CLASS);
if(parent->spec_attr == NULL) {
lv_obj_allocate_spec_attr(parent);
}
parent->spec_attr->child_cnt++;
parent->spec_attr->children = lv_realloc(parent->spec_attr->children,
sizeof(lv_obj_t *) * parent->spec_attr->child_cnt);
parent->spec_attr->children[parent->spec_attr->child_cnt - 1] = obj;
}
return obj;
}
void lv_obj_class_init_obj(lv_obj_t * obj)
{
if(obj == NULL) return;
lv_obj_mark_layout_as_dirty(obj);
lv_obj_enable_style_refresh(false);
lv_theme_apply(obj);
lv_obj_construct(obj->class_p, obj);
lv_obj_enable_style_refresh(true);
lv_obj_refresh_style(obj, LV_PART_ANY, LV_STYLE_PROP_ANY);
lv_obj_refresh_self_size(obj);
lv_group_t * def_group = lv_group_get_default();
if(def_group && lv_obj_is_group_def(obj)) {
lv_group_add_obj(def_group, obj);
}
lv_obj_t * parent = lv_obj_get_parent(obj);
if(parent) {
/*Call the ancestor's event handler to the parent to notify it about the new child.
*Also triggers layout update*/
lv_obj_send_event(parent, LV_EVENT_CHILD_CHANGED, obj);
lv_obj_send_event(parent, LV_EVENT_CHILD_CREATED, obj);
/*Invalidate the area if not screen created*/
lv_obj_invalidate(obj);
}
}
void lv_obj_destruct(lv_obj_t * obj)
{
#if LV_USE_EXT_DATA
if(obj->ext_data.free_cb) {
obj->ext_data.free_cb(obj->ext_data.data);
obj->ext_data.data = NULL;
}
#endif
if(obj->class_p->destructor_cb) obj->class_p->destructor_cb(obj->class_p, obj);
if(obj->class_p->base_class) {
/*Don't let the descendant methods run during destructing the ancestor type*/
obj->class_p = obj->class_p->base_class;
/*Call the base class's destructor too*/
lv_obj_destruct(obj);
}
}
bool lv_obj_is_editable(lv_obj_t * obj)
{
const lv_obj_class_t * class_p = obj->class_p;
/*Find a base in which editable is set*/
while(class_p && class_p->editable == LV_OBJ_CLASS_EDITABLE_INHERIT) class_p = class_p->base_class;
if(class_p == NULL) return false;
return class_p->editable == LV_OBJ_CLASS_EDITABLE_TRUE;
}
bool lv_obj_is_group_def(lv_obj_t * obj)
{
const lv_obj_class_t * class_p = obj->class_p;
/*Find a base in which group_def is set*/
while(class_p && class_p->group_def == LV_OBJ_CLASS_GROUP_DEF_INHERIT) class_p = class_p->base_class;
if(class_p == NULL) return false;
return class_p->group_def == LV_OBJ_CLASS_GROUP_DEF_TRUE;
}
#if LV_USE_EXT_DATA
void lv_obj_set_external_data(lv_obj_t * obj, void * data, void (* free_cb)(void * data))
{
if(!obj) {
LV_LOG_WARN("Can't attach external user data and destructor callback to a NULL object");
return;
}
obj->ext_data.data = data;
obj->ext_data.free_cb = free_cb;
}
#endif
/**********************
* STATIC FUNCTIONS
**********************/
static void lv_obj_construct(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
if(LV_USE_OBJ_NAME) {
LV_ASSERT_NULL(class_p->name);
}
#if LV_USE_EXT_DATA
obj->ext_data.free_cb = NULL;
obj->ext_data.data = NULL;
#endif
if(obj->class_p->base_class) {
const lv_obj_class_t * original_class_p = obj->class_p;
/*Don't let the descendant methods run during constructing the ancestor type*/
obj->class_p = obj->class_p->base_class;
/*Construct the base first*/
lv_obj_construct(class_p, obj);
/*Restore the original class*/
obj->class_p = original_class_p;
}
if(obj->class_p->constructor_cb) obj->class_p->constructor_cb(class_p, obj);
}
static uint32_t get_instance_size(const lv_obj_class_t * class_p)
{
/*Find a base in which instance size is set*/
const lv_obj_class_t * base = class_p;
while(base && base->instance_size == 0) base = base->base_class;
if(base == NULL) return 0; /*Never happens: set at least in `lv_obj` class*/
return base->instance_size;
}

View File

@@ -0,0 +1,89 @@
/**
* @file lv_obj_class.h
*
*/
#ifndef LV_OBJ_CLASS_H
#define LV_OBJ_CLASS_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../misc/lv_types.h"
#include "../misc/lv_area.h"
#include "lv_obj_property.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef enum {
LV_OBJ_CLASS_EDITABLE_INHERIT, /**< Check the base class. Must have 0 value to let zero initialized class inherit*/
LV_OBJ_CLASS_EDITABLE_TRUE,
LV_OBJ_CLASS_EDITABLE_FALSE,
} lv_obj_class_editable_t;
typedef enum {
LV_OBJ_CLASS_GROUP_DEF_INHERIT, /**< Check the base class. Must have 0 value to let zero initialized class inherit*/
LV_OBJ_CLASS_GROUP_DEF_TRUE,
LV_OBJ_CLASS_GROUP_DEF_FALSE,
} lv_obj_class_group_def_t;
typedef enum {
LV_OBJ_CLASS_THEME_INHERITABLE_FALSE, /**< Do not inherit theme from base class. */
LV_OBJ_CLASS_THEME_INHERITABLE_TRUE,
} lv_obj_class_theme_inheritable_t;
typedef void (*lv_obj_class_event_cb_t)(lv_obj_class_t * class_p, lv_event_t * e);
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create an object form a class descriptor
* @param class_p pointer to a class
* @param parent pointer to an object where the new object should be created
* @return pointer to the created object
*/
lv_obj_t * lv_obj_class_create_obj(const lv_obj_class_t * class_p, lv_obj_t * parent);
void lv_obj_class_init_obj(lv_obj_t * obj);
bool lv_obj_is_editable(lv_obj_t * obj);
bool lv_obj_is_group_def(lv_obj_t * obj);
#if LV_USE_EXT_DATA
/**
* @brief Associates an array of external data pointers with an LVGL object
*
* Associates custom user data with an LVGL object and specifies a destructor function
* that will be automatically invoked when the object is deleted to properly clean up
* the associated resources.
*
* @param obj Target LVGL object
* @param data User-defined data pointer to associate with a object
* @param free_cb Cleanup function called for each non-NULL data pointer during
* object deletion. Receives single data pointer as parameter.
* NULL means no automatic cleanup.
*/
void lv_obj_set_external_data(lv_obj_t * obj, void * data, void (* free_cb)(void * data));
#endif
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_OBJ_CLASS_H*/

View File

@@ -0,0 +1,78 @@
/**
* @file lv_obj_class_private.h
*
*/
#ifndef LV_OBJ_CLASS_PRIVATE_H
#define LV_OBJ_CLASS_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_obj_class.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**
* Describe the common methods of every object.
* Similar to a C++ class.
*/
struct _lv_obj_class_t {
const lv_obj_class_t * base_class;
/** class_p is the final class while obj->class_p is the class currently being [de]constructed. */
void (*constructor_cb)(const lv_obj_class_t * class_p, lv_obj_t * obj);
void (*destructor_cb)(const lv_obj_class_t * class_p, lv_obj_t * obj);
/** class_p is the class in which event is being processed. */
void (*event_cb)(const lv_obj_class_t * class_p, lv_event_t * e); /**< Widget type specific event function*/
#if LV_USE_OBJ_PROPERTY
uint32_t prop_index_start;
uint32_t prop_index_end;
const lv_property_ops_t * properties;
uint32_t properties_count;
#if LV_USE_OBJ_PROPERTY_NAME
/* An array of property ID and name */
const lv_property_name_t * property_names;
uint32_t names_count;
#endif
#endif
void * user_data;
const char * name;
int32_t width_def;
int32_t height_def;
uint32_t editable : 2; /**< Value from ::lv_obj_class_editable_t*/
uint32_t group_def : 2; /**< Value from ::lv_obj_class_group_def_t*/
uint32_t instance_size : 16;
uint32_t theme_inheritable : 1; /**< Value from ::lv_obj_class_theme_inheritable_t*/
};
/**********************
* GLOBAL PROTOTYPES
**********************/
void lv_obj_destruct(lv_obj_t * obj);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_OBJ_CLASS_PRIVATE_H*/

View File

@@ -0,0 +1,512 @@
/**
* @file lv_obj_draw.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_obj_draw_private.h"
#include "lv_obj_private.h"
#include "lv_obj_style.h"
#include "../display/lv_display.h"
#include "../indev/lv_indev.h"
#include "../stdlib/lv_string.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS (&lv_obj_class)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static inline lv_opa_t get_layer_opa(const lv_obj_t * obj, lv_part_t part, const lv_draw_dsc_base_t * base_dsc);
static lv_color_t normal_apply_layer_recolor(const lv_obj_t * obj, lv_part_t part, const lv_draw_dsc_base_t * base_dsc,
lv_color_t color);
static lv_color32_t image_apply_layer_recolor(const lv_obj_t * obj, lv_part_t part,
const lv_draw_dsc_base_t * base_dsc, lv_color_t color, lv_opa_t opa);
static void drop_shadow_init(const lv_obj_t * obj, lv_part_t part, lv_draw_dsc_base_t * base_dsc);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_obj_init_draw_rect_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_rect_dsc_t * draw_dsc)
{
LV_PROFILER_DRAW_BEGIN;
draw_dsc->base.obj = obj;
draw_dsc->base.part = part;
lv_opa_t opa = get_layer_opa(obj, part, &draw_dsc->base);
if(part != LV_PART_MAIN) {
if(opa <= LV_OPA_MIN) {
draw_dsc->bg_opa = LV_OPA_TRANSP;
draw_dsc->bg_image_opa = LV_OPA_TRANSP;
draw_dsc->border_opa = LV_OPA_TRANSP;
draw_dsc->outline_opa = LV_OPA_TRANSP;
draw_dsc->shadow_opa = LV_OPA_TRANSP;
LV_PROFILER_DRAW_END;
return;
}
}
draw_dsc->radius = lv_obj_get_style_radius(obj, part);
if(draw_dsc->bg_opa != LV_OPA_TRANSP) {
draw_dsc->bg_opa = lv_obj_get_style_bg_opa(obj, part);
if(draw_dsc->bg_opa > LV_OPA_MIN) {
lv_color_t bg_color = lv_obj_get_style_bg_color_filtered(obj, part);
draw_dsc->bg_color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, bg_color);
const lv_grad_dsc_t * grad = lv_obj_get_style_bg_grad(obj, part);
if(grad && grad->dir != LV_GRAD_DIR_NONE) {
lv_memcpy(&draw_dsc->bg_grad, grad, sizeof(*grad));
}
else {
draw_dsc->bg_grad.dir = lv_obj_get_style_bg_grad_dir(obj, part);
if(draw_dsc->bg_grad.dir != LV_GRAD_DIR_NONE) {
draw_dsc->bg_grad.stops[0].color = draw_dsc->bg_color;
lv_color_t bg_grad_color = lv_obj_get_style_bg_grad_color_filtered(obj, part);
draw_dsc->bg_grad.stops[1].color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, bg_grad_color);
draw_dsc->bg_grad.stops[0].frac = lv_obj_get_style_bg_main_stop(obj, part);
draw_dsc->bg_grad.stops[1].frac = lv_obj_get_style_bg_grad_stop(obj, part);
draw_dsc->bg_grad.stops[0].opa = lv_obj_get_style_bg_main_opa(obj, part);
draw_dsc->bg_grad.stops[1].opa = lv_obj_get_style_bg_grad_opa(obj, part);
}
}
}
}
if(draw_dsc->border_opa != LV_OPA_TRANSP) {
draw_dsc->border_width = lv_obj_get_style_border_width(obj, part);
if(draw_dsc->border_width) {
draw_dsc->border_opa = lv_obj_get_style_border_opa(obj, part);
if(draw_dsc->border_opa > LV_OPA_MIN) {
draw_dsc->border_side = lv_obj_get_style_border_side(obj, part);
lv_color_t border_color = lv_obj_get_style_border_color_filtered(obj, part);
draw_dsc->border_color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, border_color);
}
}
}
if(draw_dsc->outline_opa != LV_OPA_TRANSP) {
draw_dsc->outline_width = lv_obj_get_style_outline_width(obj, part);
if(draw_dsc->outline_width) {
draw_dsc->outline_opa = lv_obj_get_style_outline_opa(obj, part);
if(draw_dsc->outline_opa > LV_OPA_MIN) {
draw_dsc->outline_pad = lv_obj_get_style_outline_pad(obj, part);
lv_color_t outline_color = lv_obj_get_style_outline_color_filtered(obj, part);
draw_dsc->outline_color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, outline_color);
}
}
}
if(draw_dsc->bg_image_opa != LV_OPA_TRANSP) {
draw_dsc->bg_image_src = lv_obj_get_style_bg_image_src(obj, part);
if(draw_dsc->bg_image_src) {
draw_dsc->bg_image_opa = lv_obj_get_style_bg_image_opa(obj, part);
if(draw_dsc->bg_image_opa > LV_OPA_MIN) {
if(lv_image_src_get_type(draw_dsc->bg_image_src) == LV_IMAGE_SRC_SYMBOL) {
draw_dsc->bg_image_symbol_font = lv_obj_get_style_text_font(obj, part);
lv_color_t text_color = lv_obj_get_style_text_color_filtered(obj, part);
draw_dsc->bg_image_recolor = normal_apply_layer_recolor(obj, part, &draw_dsc->base, text_color);
}
else {
lv_color_t bg_image_recolor = lv_obj_get_style_bg_image_recolor_filtered(obj, part);
lv_opa_t bg_image_recolor_opa = lv_obj_get_style_bg_image_recolor_opa(obj, part);
lv_color32_t result = image_apply_layer_recolor(obj, part, &draw_dsc->base, bg_image_recolor, bg_image_recolor_opa);
draw_dsc->bg_image_recolor_opa = result.alpha;
draw_dsc->bg_image_recolor = lv_color_make(result.red, result.green, result.blue);
draw_dsc->bg_image_tiled = lv_obj_get_style_bg_image_tiled(obj, part);
}
draw_dsc->bg_image_colorkey = lv_obj_get_style_image_colorkey(obj, part);
}
}
}
if(draw_dsc->shadow_opa) {
draw_dsc->shadow_width = lv_obj_get_style_shadow_width(obj, part);
if(draw_dsc->shadow_width) {
if(draw_dsc->shadow_opa > LV_OPA_MIN) {
draw_dsc->shadow_opa = lv_obj_get_style_shadow_opa(obj, part);
if(draw_dsc->shadow_opa > LV_OPA_MIN) {
draw_dsc->shadow_offset_x = lv_obj_get_style_shadow_offset_x(obj, part);
draw_dsc->shadow_offset_y = lv_obj_get_style_shadow_offset_y(obj, part);
draw_dsc->shadow_spread = lv_obj_get_style_shadow_spread(obj, part);
lv_color_t shadow_color = lv_obj_get_style_shadow_color_filtered(obj, part);
draw_dsc->shadow_color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, shadow_color);
}
}
}
}
if(opa < LV_OPA_MAX) {
draw_dsc->bg_opa = LV_OPA_MIX2(draw_dsc->bg_opa, opa);
draw_dsc->bg_image_opa = LV_OPA_MIX2(draw_dsc->bg_image_opa, opa);
draw_dsc->border_opa = LV_OPA_MIX2(draw_dsc->border_opa, opa);
draw_dsc->shadow_opa = LV_OPA_MIX2(draw_dsc->shadow_opa, opa);
draw_dsc->outline_opa = LV_OPA_MIX2(draw_dsc->outline_opa, opa);
}
drop_shadow_init(obj, part, &draw_dsc->base);
LV_PROFILER_DRAW_END;
}
void lv_obj_init_draw_label_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_label_dsc_t * draw_dsc)
{
LV_PROFILER_DRAW_BEGIN;
draw_dsc->base.obj = obj;
draw_dsc->base.part = part;
draw_dsc->opa = lv_obj_get_style_text_opa(obj, part);
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
lv_opa_t opa = get_layer_opa(obj, part, &draw_dsc->base);
if(opa < LV_OPA_MAX) {
draw_dsc->opa = LV_OPA_MIX2(draw_dsc->opa, opa);
}
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
lv_color_t text_color = lv_obj_get_style_text_color_filtered(obj, part);
draw_dsc->color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, text_color);
draw_dsc->letter_space = lv_obj_get_style_text_letter_space(obj, part);
draw_dsc->line_space = lv_obj_get_style_text_line_space(obj, part);
draw_dsc->decor = lv_obj_get_style_text_decor(obj, part);
draw_dsc->font = lv_obj_get_style_text_font(obj, part);
#if LV_USE_BIDI
draw_dsc->bidi_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN);
#endif
draw_dsc->align = lv_obj_get_style_text_align(obj, part);
drop_shadow_init(obj, part, &draw_dsc->base);
LV_PROFILER_DRAW_END;
}
void lv_obj_init_draw_image_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_image_dsc_t * draw_dsc)
{
LV_PROFILER_DRAW_BEGIN;
draw_dsc->base.obj = obj;
draw_dsc->base.part = part;
draw_dsc->opa = lv_obj_get_style_image_opa(obj, part);
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
lv_opa_t opa = get_layer_opa(obj, part, &draw_dsc->base);
if(opa < LV_OPA_MAX) {
draw_dsc->opa = LV_OPA_MIX2(draw_dsc->opa, opa);
}
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
draw_dsc->rotation = 0;
draw_dsc->scale_x = LV_SCALE_NONE;
draw_dsc->scale_y = LV_SCALE_NONE;
draw_dsc->pivot.x = lv_area_get_width(&obj->coords) / 2;
draw_dsc->pivot.y = lv_area_get_height(&obj->coords) / 2;
lv_color_t recolor = lv_obj_get_style_image_recolor_filtered(obj, part);
lv_opa_t recolor_opa = lv_obj_get_style_image_recolor_opa(obj, part);
lv_color32_t result = image_apply_layer_recolor(obj, part, &draw_dsc->base, recolor, recolor_opa);
draw_dsc->recolor_opa = result.alpha;
draw_dsc->recolor = lv_color_make(result.red, result.green, result.blue);
draw_dsc->colorkey = lv_obj_get_style_image_colorkey(obj, part);
if(part != LV_PART_MAIN) draw_dsc->blend_mode = lv_obj_get_style_blend_mode(obj, part);
drop_shadow_init(obj, part, &draw_dsc->base);
LV_PROFILER_DRAW_END;
}
void lv_obj_init_draw_line_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_line_dsc_t * draw_dsc)
{
LV_PROFILER_DRAW_BEGIN;
draw_dsc->base.obj = obj;
draw_dsc->base.part = part;
draw_dsc->opa = lv_obj_get_style_line_opa(obj, part);
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
lv_opa_t opa = get_layer_opa(obj, part, &draw_dsc->base);
if(opa < LV_OPA_MAX) {
draw_dsc->opa = LV_OPA_MIX2(draw_dsc->opa, opa);
}
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
draw_dsc->width = lv_obj_get_style_line_width(obj, part);
if(draw_dsc->width == 0) {
LV_PROFILER_DRAW_END;
return;
}
lv_color_t line_color = lv_obj_get_style_line_color_filtered(obj, part);
draw_dsc->color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, line_color);
draw_dsc->dash_width = lv_obj_get_style_line_dash_width(obj, part);
if(draw_dsc->dash_width) {
draw_dsc->dash_gap = lv_obj_get_style_line_dash_gap(obj, part);
}
draw_dsc->round_start = lv_obj_get_style_line_rounded(obj, part);
draw_dsc->round_end = draw_dsc->round_start;
drop_shadow_init(obj, part, &draw_dsc->base);
LV_PROFILER_DRAW_END;
}
void lv_obj_init_draw_arc_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_arc_dsc_t * draw_dsc)
{
LV_PROFILER_DRAW_BEGIN;
draw_dsc->base.obj = obj;
draw_dsc->base.part = part;
draw_dsc->width = lv_obj_get_style_arc_width(obj, part);
if(draw_dsc->width == 0) {
LV_PROFILER_DRAW_END;
return;
}
draw_dsc->opa = lv_obj_get_style_arc_opa(obj, part);
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
lv_opa_t opa = get_layer_opa(obj, part, &draw_dsc->base);
if(opa < LV_OPA_MAX) {
draw_dsc->opa = LV_OPA_MIX2(draw_dsc->opa, opa);
}
if(draw_dsc->opa <= LV_OPA_MIN) {
LV_PROFILER_DRAW_END;
return;
}
lv_color_t arc_color = lv_obj_get_style_arc_color_filtered(obj, part);
draw_dsc->color = normal_apply_layer_recolor(obj, part, &draw_dsc->base, arc_color);
draw_dsc->img_src = lv_obj_get_style_arc_image_src(obj, part);
draw_dsc->rounded = lv_obj_get_style_arc_rounded(obj, part);
drop_shadow_init(obj, part, &draw_dsc->base);
LV_PROFILER_DRAW_END;
}
void lv_obj_init_draw_blur_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_blur_dsc_t * draw_dsc)
{
LV_PROFILER_DRAW_BEGIN;
draw_dsc->base.obj = obj;
draw_dsc->base.part = part;
draw_dsc->blur_radius = lv_obj_get_style_blur_radius(obj, part);
draw_dsc->quality = lv_obj_get_style_blur_quality(obj, part);
/*Radius might be set earlier as it's already known*/
if(draw_dsc->corner_radius == 0) {
draw_dsc->corner_radius = lv_obj_get_style_radius(obj, part);
}
LV_PROFILER_DRAW_END;
}
int32_t lv_obj_calculate_ext_draw_size(lv_obj_t * obj, lv_part_t part)
{
LV_PROFILER_DRAW_BEGIN;
int32_t s = 0;
int32_t sh_width = lv_obj_get_style_shadow_width(obj, part);
if(sh_width) {
lv_opa_t sh_opa = lv_obj_get_style_shadow_opa(obj, part);
if(sh_opa > LV_OPA_MIN) {
sh_width = sh_width / 2 + 1; /*The blur adds only half width*/
sh_width += lv_obj_get_style_shadow_spread(obj, part);
int32_t sh_ofs_x = lv_obj_get_style_shadow_offset_x(obj, part);
int32_t sh_ofs_y = lv_obj_get_style_shadow_offset_y(obj, part);
sh_width += LV_MAX(LV_ABS(sh_ofs_x), LV_ABS(sh_ofs_y));
s = LV_MAX(s, sh_width);
}
}
int32_t outline_width = lv_obj_get_style_outline_width(obj, part);
if(outline_width) {
lv_opa_t outline_opa = lv_obj_get_style_outline_opa(obj, part);
if(outline_opa > LV_OPA_MIN) {
int32_t outline_pad = lv_obj_get_style_outline_pad(obj, part);
s = LV_MAX(s, outline_pad + outline_width);
}
}
int32_t drop_shadow_size = 0;
if(lv_obj_get_style_drop_shadow_opa(obj, part) > 0) {
drop_shadow_size += LV_MAX(LV_ABS(lv_obj_get_style_drop_shadow_offset_x(obj, part)),
LV_ABS(lv_obj_get_style_drop_shadow_offset_y(obj, part)));
drop_shadow_size += lv_obj_get_style_drop_shadow_radius(obj, part) + 1;
}
s += drop_shadow_size;
int32_t w = lv_obj_get_style_transform_width(obj, part);
int32_t h = lv_obj_get_style_transform_height(obj, part);
int32_t wh = LV_MAX(w, h);
if(wh > 0) s += wh;
LV_PROFILER_DRAW_END;
return s;
}
void lv_obj_refresh_ext_draw_size(lv_obj_t * obj)
{
LV_PROFILER_DRAW_BEGIN;
LV_ASSERT_OBJ(obj, MY_CLASS);
int32_t s_old = lv_obj_get_ext_draw_size(obj);
int32_t s_new = 0;
lv_obj_send_event(obj, LV_EVENT_REFR_EXT_DRAW_SIZE, &s_new);
/*Store the result if the special attrs already allocated*/
if(obj->spec_attr) {
obj->spec_attr->ext_draw_size = s_new;
}
/*Allocate spec. attrs. only if the result is not zero.
*Zero is the default value if the spec. attr. are not defined.*/
else if(s_new != 0) {
lv_obj_allocate_spec_attr(obj);
obj->spec_attr->ext_draw_size = s_new;
}
if(s_new != s_old) lv_obj_invalidate(obj);
LV_PROFILER_DRAW_END;
}
int32_t lv_obj_get_ext_draw_size(const lv_obj_t * obj)
{
if(obj->spec_attr) return obj->spec_attr->ext_draw_size;
else return 0;
}
lv_layer_type_t lv_obj_get_layer_type(const lv_obj_t * obj)
{
if(obj->spec_attr) return (lv_layer_type_t)obj->spec_attr->layer_type;
else return LV_LAYER_TYPE_NONE;
}
/**********************
* STATIC FUNCTIONS
**********************/
static inline lv_opa_t get_layer_opa(const lv_obj_t * obj, lv_part_t part, const lv_draw_dsc_base_t * base_dsc)
{
if(base_dsc->layer) {
/* Accessing the layer opa directly is faster than using get style opa recursive */
if(part == LV_PART_MAIN) {
return base_dsc->layer->opa;
}
return LV_OPA_MIX2(base_dsc->layer->opa, lv_obj_get_style_opa(obj, part));
}
/* fallback to old recursive style opa */
return lv_obj_get_style_opa_recursive(obj, part);
}
static lv_color_t normal_apply_layer_recolor(const lv_obj_t * obj, lv_part_t part, const lv_draw_dsc_base_t * base_dsc,
lv_color_t color)
{
lv_color32_t recolor;
if(base_dsc->layer) {
recolor = base_dsc->layer->recolor;
if(part != LV_PART_MAIN) {
recolor = lv_obj_style_apply_recolor(obj, part, recolor);
}
}
else {
recolor = lv_obj_get_style_recolor_recursive(obj, part);
}
return lv_color_mix(lv_color_make(recolor.red, recolor.green, recolor.blue), color, recolor.alpha);
}
static lv_color32_t image_apply_layer_recolor(const lv_obj_t * obj, lv_part_t part,
const lv_draw_dsc_base_t * base_dsc, lv_color_t color, lv_opa_t opa)
{
lv_color32_t recolor;
if(base_dsc->layer) {
recolor = base_dsc->layer->recolor;
if(part != LV_PART_MAIN) {
recolor = lv_obj_style_apply_recolor(obj, part, recolor);
}
}
else {
recolor = lv_obj_get_style_recolor_recursive(obj, part);
}
if(opa > LV_OPA_TRANSP && recolor.alpha > LV_OPA_TRANSP) {
return lv_color_over32(recolor, lv_color_to_32(color, opa));
}
else if(recolor.alpha > LV_OPA_TRANSP) {
return recolor;
}
else {
return lv_color_to_32(color, opa);
}
}
static void drop_shadow_init(const lv_obj_t * obj, lv_part_t part, lv_draw_dsc_base_t * base_dsc)
{
base_dsc->drop_shadow_opa = lv_obj_get_style_drop_shadow_opa(obj, part);
if(base_dsc->drop_shadow_opa) {
base_dsc->drop_shadow_blur_radius = lv_obj_get_style_drop_shadow_radius(obj, part);
base_dsc->drop_shadow_ofs_x = lv_obj_get_style_drop_shadow_offset_x(obj, part);
base_dsc->drop_shadow_ofs_y = lv_obj_get_style_drop_shadow_offset_y(obj, part);
base_dsc->drop_shadow_color = lv_obj_get_style_drop_shadow_color(obj, part);
base_dsc->drop_shadow_color = normal_apply_layer_recolor(obj, part, base_dsc, base_dsc->drop_shadow_color);
base_dsc->drop_shadow_quality = lv_obj_get_style_drop_shadow_quality(obj, part);
}
}

View File

@@ -0,0 +1,142 @@
/**
* @file lv_obj_draw.h
*
*/
#ifndef LV_OBJ_DRAW_H
#define LV_OBJ_DRAW_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../misc/lv_types.h"
#include "../draw/lv_draw_rect.h"
#include "../draw/lv_draw_label.h"
#include "../draw/lv_draw_image.h"
#include "../draw/lv_draw_line.h"
#include "../draw/lv_draw_arc.h"
#include "../draw/lv_draw_triangle.h"
#include "../draw/lv_draw_blur.h"
#include "lv_obj_style.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/** Store the type of layer required to render a widget.*/
typedef enum {
/**No layer is needed. */
LV_LAYER_TYPE_NONE,
/**Simple layer means that the layer can be rendered in chunks.
* For example with opa_layered = 140 it's possible to render only 10 lines
* from the layer. When it's ready go to the next 10 lines.
* It avoids large memory allocations for the layer buffer.
* The buffer size for a chunk can be set by `LV_DRAW_LAYER_SIMPLE_BUF_SIZE` in lv_conf.h.*/
LV_LAYER_TYPE_SIMPLE,
/**The widget is transformed and cannot be rendered in chunks.
* It's because - due to the transformations - pixel outside of
* a given area will also contribute to the final image.
* In this case there is no limitation on the buffer size.
* LVGL will allocate as large buffer as needed to render the transformed area.*/
LV_LAYER_TYPE_TRANSFORM,
} lv_layer_type_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Initialize a rectangle draw descriptor from an object's styles in its current state
* @param obj pointer to an object
* @param part part of the object, e.g. `LV_PART_MAIN`, `LV_PART_SCROLLBAR`, `LV_PART_KNOB`, etc
* @param draw_dsc the descriptor to initialize.
* If an `..._opa` field is set to `LV_OPA_TRANSP` the related properties won't be initialized.
* Should be initialized with `lv_draw_rect_dsc_init(draw_dsc)`.
* @note Only the relevant fields will be set.
* E.g. if `border width == 0` the other border properties won't be evaluated.
*/
void lv_obj_init_draw_rect_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_rect_dsc_t * draw_dsc);
/**
* Initialize a label draw descriptor from an object's styles in its current state
* @param obj pointer to an object
* @param part part of the object, e.g. `LV_PART_MAIN`, `LV_PART_SCROLLBAR`, `LV_PART_KNOB`, etc
* @param draw_dsc the descriptor to initialize.
* If the `opa` field is set to or the property is equal to `LV_OPA_TRANSP` the rest won't be initialized.
* Should be initialized with `lv_draw_label_dsc_init(draw_dsc)`.
*/
void lv_obj_init_draw_label_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_label_dsc_t * draw_dsc);
/**
* Initialize an image draw descriptor from an object's styles in its current state
* @param obj pointer to an object
* @param part part of the object, e.g. `LV_PART_MAIN`, `LV_PART_SCROLLBAR`, `LV_PART_KNOB`, etc
* @param draw_dsc the descriptor to initialize.
* Should be initialized with `lv_draw_image_dsc_init(draw_dsc)`.
*/
void lv_obj_init_draw_image_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_image_dsc_t * draw_dsc);
/**
* Initialize a line draw descriptor from an object's styles in its current state
* @param obj pointer to an object
* @param part part of the object, e.g. `LV_PART_MAIN`, `LV_PART_SCROLLBAR`, `LV_PART_KNOB`, etc
* @param draw_dsc the descriptor to initialize.
* Should be initialized with `lv_draw_line_dsc_init(draw_dsc)`.
*/
void lv_obj_init_draw_line_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_line_dsc_t * draw_dsc);
/**
* Initialize an arc draw descriptor from an object's styles in its current state
* @param obj pointer to an object
* @param part part of the object, e.g. `LV_PART_MAIN`, `LV_PART_SCROLLBAR`, `LV_PART_KNOB`, etc
* @param draw_dsc the descriptor to initialize.
* Should be initialized with `lv_draw_arc_dsc_init(draw_dsc)`.
*/
void lv_obj_init_draw_arc_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_arc_dsc_t * draw_dsc);
/**
* Initialize a blur draw descriptor from an object's styles in its current state.
* draw_dsc->radius will only be calculated if it's 0 initially. Radius can be set before calling this function
* to avoid getting it twice.
* @param obj pointer to an object
* @param part part of the object, e.g. `LV_PART_MAIN`, `LV_PART_SCROLLBAR`, `LV_PART_KNOB`, etc
* @param draw_dsc the descriptor to initialize.
* Should be initialized with `lv_draw_blur_dsc_init(draw_dsc)`.
*/
void lv_obj_init_draw_blur_dsc(lv_obj_t * obj, lv_part_t part, lv_draw_blur_dsc_t * draw_dsc);
/**
* Get the required extra size (around the object's part) to draw shadow, outline, value etc.
* @param obj pointer to an object
* @param part part of the object
* @return the extra size required around the object
*/
int32_t lv_obj_calculate_ext_draw_size(lv_obj_t * obj, lv_part_t part);
/**
* Send a 'LV_EVENT_REFR_EXT_DRAW_SIZE' Call the ancestor's event handler to the object to refresh the value of the extended draw size.
* The result will be saved in `obj`.
* @param obj pointer to an object
*/
void lv_obj_refresh_ext_draw_size(lv_obj_t * obj);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_OBJ_DRAW_H*/

View File

@@ -0,0 +1,48 @@
/**
* @file lv_obj_draw_private.h
*
*/
#ifndef LV_OBJ_DRAW_PRIVATE_H
#define LV_OBJ_DRAW_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_obj_draw.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Get the extended draw area of an object.
* @param obj pointer to an object
* @return the size extended draw area around the real coordinates
*/
int32_t lv_obj_get_ext_draw_size(const lv_obj_t * obj);
lv_layer_type_t lv_obj_get_layer_type(const lv_obj_t * obj);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_OBJ_DRAW_PRIVATE_H*/

View File

@@ -0,0 +1,491 @@
/**
* @file lv_obj_event.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../misc/lv_event_private.h"
#include "lv_obj_event_private.h"
#include "lv_obj_class_private.h"
#include "lv_obj_private.h"
#include "../indev/lv_indev.h"
#include "../indev/lv_indev_private.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS (&lv_obj_class)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static lv_result_t event_send_core(lv_event_t * e);
static bool event_is_bubbled(lv_event_t * e);
static bool event_is_trickled(lv_event_t * e);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#if LV_USE_LOG && LV_LOG_TRACE_EVENT
#define LV_TRACE_EVENT(...) LV_LOG_TRACE(__VA_ARGS__)
#else
#define LV_TRACE_EVENT(...)
#endif
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_result_t lv_obj_send_event(lv_obj_t * obj, lv_event_code_t event_code, void * param)
{
if(obj == NULL) return LV_RESULT_OK;
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_event_t e;
e.current_target = obj;
e.original_target = obj;
e.code = event_code;
e.user_data = NULL;
e.param = param;
e.deleted = 0;
e.stop_bubbling = 0;
e.stop_processing = 0;
e.stop_trickling = 0;
lv_event_push(&e);
/*Send the event*/
lv_result_t res = event_send_core(&e);
/*Remove this element from the list*/
lv_event_pop(&e);
return res;
}
lv_result_t lv_obj_event_base(const lv_obj_class_t * class_p, lv_event_t * e)
{
const lv_obj_class_t * base;
if(class_p == NULL) base = ((lv_obj_t *)e->current_target)->class_p;
else base = class_p->base_class;
/*Find a base in which call the ancestor's event handler_cb if set*/
while(base && base->event_cb == NULL) base = base->base_class;
if(base == NULL) return LV_RESULT_OK;
if(base->event_cb == NULL) return LV_RESULT_OK;
/*Call the actual event callback*/
e->user_data = NULL;
LV_PROFILER_EVENT_BEGIN_TAG(lv_event_code_get_name(e->code));
base->event_cb(base, e);
LV_PROFILER_EVENT_END_TAG(lv_event_code_get_name(e->code));
lv_result_t res = LV_RESULT_OK;
/*Stop if the object is deleted*/
if(e->deleted) res = LV_RESULT_INVALID;
return res;
}
lv_event_dsc_t * lv_obj_add_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb, lv_event_code_t filter, void * user_data)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_obj_allocate_spec_attr(obj);
return lv_event_add(&obj->spec_attr->event_list, event_cb, filter, user_data);
}
uint32_t lv_obj_get_event_count(lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
if(obj->spec_attr == NULL) return 0;
return lv_event_get_count(&obj->spec_attr->event_list);
}
lv_event_dsc_t * lv_obj_get_event_dsc(lv_obj_t * obj, uint32_t index)
{
LV_ASSERT_NULL(obj);
if(obj->spec_attr == NULL) return NULL;
return lv_event_get_dsc(&obj->spec_attr->event_list, index);
}
bool lv_obj_remove_event(lv_obj_t * obj, uint32_t index)
{
LV_ASSERT_NULL(obj);
if(obj->spec_attr == NULL) return false;
return lv_event_remove(&obj->spec_attr->event_list, index);
}
bool lv_obj_remove_event_dsc(lv_obj_t * obj, lv_event_dsc_t * dsc)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_NULL(dsc);
if(obj->spec_attr == NULL) return false;
return lv_event_remove_dsc(&obj->spec_attr->event_list, dsc);
}
uint32_t lv_obj_remove_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb)
{
LV_ASSERT_NULL(obj);
uint32_t event_cnt = lv_obj_get_event_count(obj);
uint32_t removed_count = 0;
int32_t i;
if(event_cnt == 0) return 0;
for(i = event_cnt - 1; i >= 0; i--) {
lv_event_dsc_t * dsc = lv_obj_get_event_dsc(obj, i);
if(dsc && dsc->cb == event_cb) {
lv_obj_remove_event(obj, i);
removed_count++;
}
}
return removed_count;
}
uint32_t lv_obj_remove_event_cb_with_user_data(lv_obj_t * obj, lv_event_cb_t event_cb, void * user_data)
{
LV_ASSERT_NULL(obj);
uint32_t event_cnt = lv_obj_get_event_count(obj);
uint32_t removed_count = 0;
int32_t i;
if(event_cnt == 0) return 0;
for(i = event_cnt - 1; i >= 0; i--) {
lv_event_dsc_t * dsc = lv_obj_get_event_dsc(obj, i);
if(dsc && (event_cb == NULL || dsc->cb == event_cb) && dsc->user_data == user_data) {
lv_obj_remove_event(obj, i);
removed_count ++;
}
}
return removed_count;
}
lv_obj_t * lv_event_get_current_target_obj(lv_event_t * e)
{
return lv_event_get_current_target(e);
}
lv_obj_t * lv_event_get_target_obj(lv_event_t * e)
{
return lv_event_get_target(e);
}
lv_indev_t * lv_event_get_indev(lv_event_t * e)
{
if(e->code == LV_EVENT_PRESSED ||
e->code == LV_EVENT_PRESSING ||
e->code == LV_EVENT_PRESS_LOST ||
e->code == LV_EVENT_SHORT_CLICKED ||
e->code == LV_EVENT_LONG_PRESSED ||
e->code == LV_EVENT_LONG_PRESSED_REPEAT ||
e->code == LV_EVENT_CLICKED ||
e->code == LV_EVENT_RELEASED ||
e->code == LV_EVENT_SCROLL_BEGIN ||
e->code == LV_EVENT_SCROLL_END ||
e->code == LV_EVENT_SCROLL ||
e->code == LV_EVENT_GESTURE ||
e->code == LV_EVENT_KEY ||
e->code == LV_EVENT_FOCUSED ||
e->code == LV_EVENT_DEFOCUSED ||
e->code == LV_EVENT_LEAVE ||
e->code == LV_EVENT_HOVER_OVER ||
e->code == LV_EVENT_HOVER_LEAVE) {
return lv_event_get_param(e);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return NULL;
}
}
lv_layer_t * lv_event_get_layer(lv_event_t * e)
{
if(e->code == LV_EVENT_DRAW_MAIN ||
e->code == LV_EVENT_DRAW_MAIN_BEGIN ||
e->code == LV_EVENT_DRAW_MAIN_END ||
e->code == LV_EVENT_DRAW_POST ||
e->code == LV_EVENT_DRAW_POST_BEGIN ||
e->code == LV_EVENT_DRAW_POST_END) {
return lv_event_get_param(e);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return NULL;
}
}
const lv_area_t * lv_event_get_old_size(lv_event_t * e)
{
if(e->code == LV_EVENT_SIZE_CHANGED) {
return lv_event_get_param(e);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return NULL;
}
}
uint32_t lv_event_get_key(lv_event_t * e)
{
if(e->code == LV_EVENT_KEY) {
uint32_t * k = lv_event_get_param(e);
if(k) return *k;
else return 0;
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return 0;
}
}
int32_t lv_event_get_rotary_diff(lv_event_t * e)
{
if(e->code == LV_EVENT_ROTARY) {
int32_t * r = lv_event_get_param(e);
if(r) return *r;
else return 0;
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return 0;
}
}
lv_anim_t * lv_event_get_scroll_anim(lv_event_t * e)
{
if(e->code == LV_EVENT_SCROLL_BEGIN) {
return lv_event_get_param(e);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return NULL;
}
}
void lv_event_set_ext_draw_size(lv_event_t * e, int32_t size)
{
if(e->code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
int32_t * cur_size = lv_event_get_param(e);
*cur_size = LV_MAX(*cur_size, size);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
}
}
lv_point_t * lv_event_get_self_size_info(lv_event_t * e)
{
if(e->code == LV_EVENT_GET_SELF_SIZE) {
return lv_event_get_param(e);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return 0;
}
}
lv_hit_test_info_t * lv_event_get_hit_test_info(lv_event_t * e)
{
if(e->code == LV_EVENT_HIT_TEST) {
return lv_event_get_param(e);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return 0;
}
}
const lv_area_t * lv_event_get_cover_area(lv_event_t * e)
{
if(e->code == LV_EVENT_COVER_CHECK) {
lv_cover_check_info_t * p = lv_event_get_param(e);
return p->area;
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return NULL;
}
}
void lv_event_set_cover_res(lv_event_t * e, lv_cover_res_t res)
{
if(e->code == LV_EVENT_COVER_CHECK) {
lv_cover_check_info_t * p = lv_event_get_param(e);
if(res > p->res) p->res = res; /*Save only "stronger" results*/
}
else {
LV_LOG_WARN("Not interpreted with this event code");
}
}
lv_draw_task_t * lv_event_get_draw_task(lv_event_t * e)
{
if(e->code == LV_EVENT_DRAW_TASK_ADDED) {
return lv_event_get_param(e);
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return NULL;
}
}
lv_state_t lv_event_get_prev_state(lv_event_t * e)
{
if(e->code == LV_EVENT_STATE_CHANGED) {
lv_state_t * state = lv_event_get_param(e);
return state ? *state : 0;
}
else {
LV_LOG_WARN("Not interpreted with this event code");
return 0;
}
}
/**********************
* STATIC FUNCTIONS
**********************/
static lv_result_t event_send_core(lv_event_t * e)
{
LV_TRACE_EVENT("Sending event %d to %p with %p param", e->code, (void *)e->original_target, e->param);
lv_indev_t * indev_act = lv_indev_active();
if(indev_act) {
if(e->stop_processing) return LV_RESULT_OK;
if(e->deleted) return LV_RESULT_INVALID;
}
lv_obj_t * target = e->current_target;
lv_result_t res = LV_RESULT_OK;
lv_event_list_t * list = target->spec_attr ? &target->spec_attr->event_list : NULL;
res = lv_event_send(list, e, true);
if(res != LV_RESULT_OK || e->stop_processing) return res;
res = lv_obj_event_base(NULL, e);
if(res != LV_RESULT_OK || e->stop_processing) return res;
res = lv_event_send(list, e, false);
if(res != LV_RESULT_OK || e->stop_processing) return res;
lv_obj_t * parent = lv_obj_get_parent(e->current_target);
if(parent && event_is_bubbled(e)) {
e->current_target = parent;
res = event_send_core(e);
}
if(res != LV_RESULT_OK) return res;
/*Trickle down to children if enabled*/
if(event_is_trickled(e)) {
uint32_t child_count = lv_obj_get_child_count(target);
/* we don't want the event to bubble up again when trickling down */
e->stop_bubbling = 1;
for(uint32_t i = 0; i < child_count && res == LV_RESULT_OK && !e->stop_processing; i++) {
lv_obj_t * child = lv_obj_get_child(target, i);
if(child) {
e->current_target = child;
res = event_send_core(e);
if(res != LV_RESULT_OK) {
LV_LOG_WARN("Trickle down event %d to child %p failed", e->code, (void *)child);
break;
}
}
}
}
return res;
}
static bool event_is_bubbled(lv_event_t * e)
{
if(e->stop_bubbling) return false;
/*Event codes that always bubble*/
switch(e->code) {
case LV_EVENT_CHILD_CREATED:
case LV_EVENT_CHILD_DELETED:
return true;
default:
break;
}
/*Check other codes only if bubbling is enabled*/
if(lv_obj_has_flag(e->current_target, LV_OBJ_FLAG_EVENT_BUBBLE) == false) return false;
switch(e->code) {
case LV_EVENT_HIT_TEST:
case LV_EVENT_COVER_CHECK:
case LV_EVENT_REFR_EXT_DRAW_SIZE:
case LV_EVENT_DRAW_MAIN_BEGIN:
case LV_EVENT_DRAW_MAIN:
case LV_EVENT_DRAW_MAIN_END:
case LV_EVENT_DRAW_POST_BEGIN:
case LV_EVENT_DRAW_POST:
case LV_EVENT_DRAW_POST_END:
case LV_EVENT_DRAW_TASK_ADDED:
case LV_EVENT_REFRESH:
case LV_EVENT_DELETE:
case LV_EVENT_CHILD_CREATED:
case LV_EVENT_CHILD_DELETED:
case LV_EVENT_CHILD_CHANGED:
case LV_EVENT_SIZE_CHANGED:
case LV_EVENT_STYLE_CHANGED:
case LV_EVENT_GET_SELF_SIZE:
return false;
default:
return true;
}
}
static bool event_is_trickled(lv_event_t * e)
{
if(e->stop_trickling) return false;
/*Check other codes only if trickle is enabled*/
if(lv_obj_has_flag(e->current_target, LV_OBJ_FLAG_EVENT_TRICKLE) == false) return false;
switch(e->code) {
case LV_EVENT_HIT_TEST:
case LV_EVENT_COVER_CHECK:
case LV_EVENT_REFR_EXT_DRAW_SIZE:
case LV_EVENT_DRAW_MAIN_BEGIN:
case LV_EVENT_DRAW_MAIN:
case LV_EVENT_DRAW_MAIN_END:
case LV_EVENT_DRAW_POST_BEGIN:
case LV_EVENT_DRAW_POST:
case LV_EVENT_DRAW_POST_END:
case LV_EVENT_DRAW_TASK_ADDED:
case LV_EVENT_REFRESH:
case LV_EVENT_DELETE:
case LV_EVENT_CHILD_CREATED:
case LV_EVENT_CHILD_DELETED:
case LV_EVENT_CHILD_CHANGED:
case LV_EVENT_SIZE_CHANGED:
case LV_EVENT_STYLE_CHANGED:
case LV_EVENT_GET_SELF_SIZE:
return false;
default:
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More