From 7a113a32dd265bdd38bb6093cb24525c09e62162 Mon Sep 17 00:00:00 2001 From: TuxMonkey <8196772+tuxmonkey@user.noreply.gitee.com> Date: Mon, 3 Nov 2025 22:08:14 +0800 Subject: [PATCH] add bsp_spi --- CMakeLists.txt | 24 ++-- User_Code/bsp/fdcan/bsp_fdcan.c | 239 +++++++++++++++---------------- User_Code/bsp/fdcan/bsp_fdcan.md | 29 ++-- User_Code/bsp/spi/bsp_spi.c | 134 ++++++++++++++++- User_Code/bsp/spi/bsp_spi.h | 98 ++++++++++++- User_Code/bsp/spi/bsp_spi.md | 11 ++ 6 files changed, 385 insertions(+), 150 deletions(-) create mode 100644 User_Code/bsp/spi/bsp_spi.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 12f364a..f3f4cb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,26 +28,26 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS ON) -if(CMAKE_BUILD_TYPE STREQUAL "Debug") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_C_FLAGS_DEBUG "-O0 -g") set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") -elseif(CMAKE_BUILD_TYPE STREQUAL "Release") +elseif (CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_C_FLAGS_RELEASE "-O3") set(CMAKE_CXX_FLAGS_RELEASE "-O3") -elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") +elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") -elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") +elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") set(CMAKE_C_FLAGS_MINSIZEREL "-Os") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os") -endif() +endif () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -frtti -Wno-volatile") # 定义构建类型 -if(NOT CMAKE_BUILD_TYPE) +if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug") -endif() +endif () message("Build type: ${CMAKE_BUILD_TYPE}") # Set the project name @@ -101,9 +101,9 @@ add_subdirectory(cmake/stm32cubemx) # Add linked libraries target_link_libraries(${CMAKE_PROJECT_NAME} - stm32cubemx -# ${CMAKE_SOURCE_DIR}/Drivers/CMSIS/DSP/Lib/GCC/libarm_cortexM7lfdp_math.a - # Add user defined libraries + stm32cubemx + # ${CMAKE_SOURCE_DIR}/Drivers/CMSIS/DSP/Lib/GCC/libarm_cortexM7lfdp_math.a + # Add user defined libraries ) #link_libraries("Drivers/CMSIS/DSP/Lib/GCC/libarm_cortexM7lfdp_math.a") # @@ -164,18 +164,20 @@ target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE # Add sources to executable target_sources(${CMAKE_PROJECT_NAME} PRIVATE User_Code/bsp/fdcan/bsp_fdcan.c + User_Code/bsp/spi/bsp_spi.c # Add user sources here ) # Add include paths target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE User_Code/bsp/fdcan + User_Code/bsp/spi # Add user defined include paths ) # Add project symbols (macros) target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE -# RM_REFEREE + # RM_REFEREE # Add user defined symbols ) diff --git a/User_Code/bsp/fdcan/bsp_fdcan.c b/User_Code/bsp/fdcan/bsp_fdcan.c index ed935e1..214602a 100644 --- a/User_Code/bsp/fdcan/bsp_fdcan.c +++ b/User_Code/bsp/fdcan/bsp_fdcan.c @@ -23,34 +23,27 @@ static uint8_t idx = 0; // 全局FDCAN实例索引,每次有新的模块注册 * * @param _instance FDCAN实例 */ -static void FDCANAddFilter(FDCANInstance *_instance) -{ +static void FDCANAddFilter(FDCANInstance *_instance) { FDCAN_FilterTypeDef can_filter_conf; static uint8_t fdcan1_filter_idx = 0, fdcan2_filter_idx = 0, fdcan3_filter_idx = 0; - can_filter_conf.IdType = FDCAN_STANDARD_ID; // 标准ID - can_filter_conf.FilterType = FDCAN_FILTER_MASK; // 使用掩码模式 + can_filter_conf.IdType = FDCAN_STANDARD_ID; // 标准ID + can_filter_conf.FilterType = FDCAN_FILTER_MASK; // 使用掩码模式 // 根据can_handle判断是哪个FDCAN,然后分配过滤器索引 - if (_instance->can_handle == &hfdcan1) - { + if (_instance->can_handle == &hfdcan1) { can_filter_conf.FilterIndex = fdcan1_filter_idx++; - } - else if (_instance->can_handle == &hfdcan2) - { + } else if (_instance->can_handle == &hfdcan2) { can_filter_conf.FilterIndex = fdcan2_filter_idx++; - } - else if (_instance->can_handle == &hfdcan3) - { + } else if (_instance->can_handle == &hfdcan3) { can_filter_conf.FilterIndex = fdcan3_filter_idx++; } can_filter_conf.FilterConfig = (_instance->rx_id & 1) ? FDCAN_FILTER_TO_RXFIFO0 : FDCAN_FILTER_TO_RXFIFO1; - can_filter_conf.FilterID1 = _instance->rx_id << 5; // 标准ID左移5位 - can_filter_conf.FilterID2 = 0x7FF << 5; // 掩码: 匹配所有标准ID + can_filter_conf.FilterID1 = _instance->rx_id << 5; // 标准ID左移5位 + can_filter_conf.FilterID2 = 0x7FF << 5; // 掩码: 匹配所有标准ID - if (HAL_FDCAN_ConfigFilter(_instance->can_handle, &can_filter_conf) != HAL_OK) - { + if (HAL_FDCAN_ConfigFilter(_instance->can_handle, &can_filter_conf) != HAL_OK) { Error_Handler(); } } @@ -61,8 +54,7 @@ static void FDCANAddFilter(FDCANInstance *_instance) * @note 此函数会启动FDCAN1、FDCAN2和FDCAN3,开启相应的FIFO中断通知 * */ -static void FDCANServiceInit() -{ +static void FDCANServiceInit() { // 配置全局过滤器 - 拒绝所有不匹配的帧 HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE); HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE); @@ -91,8 +83,7 @@ static void FDCANServiceInit() * @param dlc 数据长度码 * @return uint8_t 实际数据长度 */ -static uint8_t FDCANDlcToLen(uint32_t dlc) -{ +static uint8_t FDCANDlcToLen(uint32_t dlc) { if (dlc <= FDCAN_DLC_BYTES_8) return dlc; else if (dlc == FDCAN_DLC_BYTES_12) @@ -117,8 +108,7 @@ static uint8_t FDCANDlcToLen(uint32_t dlc) * @param len 实际数据长度 * @return uint32_t 数据长度码 */ -static uint32_t FDCANLenToDlc(uint8_t len) -{ +static uint32_t FDCANLenToDlc(uint8_t len) { if (len <= 8) return len; else if (len == 12) @@ -140,10 +130,8 @@ static uint32_t FDCANLenToDlc(uint8_t len) /* ----------------------- 外部可调用函数 -----------------------*/ -FDCANInstance *FDCANRegister(FDCAN_Init_Config_s *config) -{ - if (!idx) - { +FDCANInstance *FDCANRegister(FDCAN_Init_Config_s *config) { + if (!idx) { FDCANServiceInit(); // 第一次注册,先进行硬件初始化 } if (idx >= CAN_MX_REGISTER_CNT) // 超过最大实例数 @@ -152,27 +140,26 @@ FDCANInstance *FDCANRegister(FDCAN_Init_Config_s *config) // LOGERROR("[bsp_fdcan] FDCAN instance exceeded MAX num, consider balance the load of FDCAN 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) - { + 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_fdcan] FDCAN 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,所以要先清空 // 进行发送报文的配置 - instance->txconf.Identifier = config->tx_id; // 发送id - instance->txconf.IdType = FDCAN_STANDARD_ID; // 使用标准id - instance->txconf.TxFrameType = FDCAN_DATA_FRAME; // 发送数据帧 - instance->txconf.DataLength = FDCAN_DLC_BYTES_8; // 默认发送长度为8 + instance->txconf.Identifier = config->tx_id; // 发送id + instance->txconf.IdType = FDCAN_STANDARD_ID; // 使用标准id + instance->txconf.TxFrameType = FDCAN_DATA_FRAME; // 发送数据帧 + instance->txconf.DataLength = FDCAN_DLC_BYTES_8; // 默认发送长度为8 instance->txconf.ErrorStateIndicator = FDCAN_ESI_ACTIVE; - instance->txconf.BitRateSwitch = FDCAN_BRS_OFF; // 默认关闭比特率切换 - instance->txconf.FDFormat = FDCAN_CLASSIC_CAN; // 默认经典CAN模式 + instance->txconf.BitRateSwitch = FDCAN_BRS_OFF; // 默认关闭比特率切换 + instance->txconf.FDFormat = FDCAN_CLASSIC_CAN; // 默认经典CAN模式 instance->txconf.TxEventFifoControl = FDCAN_NO_TX_EVENTS; instance->txconf.MessageMarker = 0; @@ -183,8 +170,8 @@ FDCANInstance *FDCANRegister(FDCAN_Init_Config_s *config) instance->can_module_callback = config->can_module_callback; instance->id = config->id; - FDCANAddFilter(instance); // 添加FDCAN过滤器规则 - fdcan_instance[idx++] = instance; // 将实例保存到fdcan_instance中 + FDCANAddFilter(instance); // 添加FDCAN过滤器规则 + fdcan_instance[idx++] = instance; // 将实例保存到fdcan_instance中 return instance; // 返回FDCAN实例指针 } @@ -199,15 +186,13 @@ FDCANInstance *FDCANRegister(FDCAN_Init_Config_s *config) * @param timeout 超时时间,单位为ms * @return uint8_t 发送成功返回1,失败返回0 */ -uint8_t FDCANTransmit(FDCANInstance *_instance, float timeout) -{ +uint8_t FDCANTransmit(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(); // 等待发送FIFO有空闲位置 - while (HAL_FDCAN_GetTxFifoFreeLevel(_instance->can_handle) == 0) - { + while (HAL_FDCAN_GetTxFifoFreeLevel(_instance->can_handle) == 0) { // if (DWT_GetTimeline_ms() - dwt_start > timeout) // 超时 // { // LOGWARNING("[bsp_fdcan] FDCAN TX FIFO full! failed to add msg to FIFO. Cnt [%d]", busy_count); @@ -218,8 +203,7 @@ uint8_t FDCANTransmit(FDCANInstance *_instance, float timeout) // wait_time = DWT_GetTimeline_ms() - dwt_start; // 发送消息 - if (HAL_FDCAN_AddMessageToTxFifoQ(_instance->can_handle, &_instance->txconf, _instance->tx_buff) != HAL_OK) - { + if (HAL_FDCAN_AddMessageToTxFifoQ(_instance->can_handle, &_instance->txconf, _instance->tx_buff) != HAL_OK) { // LOGWARNING("[bsp_fdcan] FDCAN bus BUS! cnt:%d", busy_count); // busy_count++; return 0; @@ -234,8 +218,7 @@ uint8_t FDCANTransmit(FDCANInstance *_instance, float timeout) * @param _instance 要修改长度的FDCAN实例 * @param length 设定长度 */ -void FDCANSetDLC(FDCANInstance *_instance, uint8_t length) -{ +void FDCANSetDLC(FDCANInstance *_instance, uint8_t length) { // 发送长度错误!检查调用参数是否出错,或出现野指针/越界访问 if (length > 64 || length == 0) // 安全检查 while (1) { @@ -250,37 +233,77 @@ void FDCANSetDLC(FDCANInstance *_instance, uint8_t length) * @param mode CAN模式:CAN_CLASS或CAN_FD_BRS * @param baud 波特率选择 */ -void bsp_fdcan_set_baud(FDCAN_HandleTypeDef *hfdcan, uint8_t mode, uint8_t baud) -{ +void bsp_fdcan_set_baud(FDCAN_HandleTypeDef *hfdcan, uint8_t mode, uint8_t baud) { uint32_t nom_brp = 0, nom_seg1 = 0, nom_seg2 = 0, nom_sjw = 0; uint32_t dat_brp = 0, dat_seg1 = 0, dat_seg2 = 0, dat_sjw = 0; - if (mode == CAN_CLASS) - { - switch (baud) - { - case CAN_BR_125K: nom_brp = 4; nom_seg1 = 139; nom_seg2 = 20; nom_sjw = 20; break; - case CAN_BR_200K: nom_brp = 2; nom_seg1 = 174; nom_seg2 = 25; nom_sjw = 25; break; - case CAN_BR_250K: nom_brp = 2; nom_seg1 = 139; nom_seg2 = 20; nom_sjw = 20; break; - case CAN_BR_500K: nom_brp = 1; nom_seg1 = 139; nom_seg2 = 20; nom_sjw = 20; break; - case CAN_BR_1M: nom_brp = 1; nom_seg1 = 59; nom_seg2 = 20; nom_sjw = 20; break; + if (mode == CAN_CLASS) { + switch (baud) { + case CAN_BR_125K: nom_brp = 4; + nom_seg1 = 139; + nom_seg2 = 20; + nom_sjw = 20; + break; + case CAN_BR_200K: nom_brp = 2; + nom_seg1 = 174; + nom_seg2 = 25; + nom_sjw = 25; + break; + case CAN_BR_250K: nom_brp = 2; + nom_seg1 = 139; + nom_seg2 = 20; + nom_sjw = 20; + break; + case CAN_BR_500K: nom_brp = 1; + nom_seg1 = 139; + nom_seg2 = 20; + nom_sjw = 20; + break; + case CAN_BR_1M: nom_brp = 1; + nom_seg1 = 59; + nom_seg2 = 20; + nom_sjw = 20; + break; default: break; } - dat_brp = 1; dat_seg1 = 29; dat_seg2 = 10; dat_sjw = 10; // 仲裁域默认1M + dat_brp = 1; + dat_seg1 = 29; + dat_seg2 = 10; + dat_sjw = 10; // 仲裁域默认1M hfdcan->Init.FrameFormat = FDCAN_FRAME_CLASSIC; - } - else if (mode == CAN_FD_BRS) - { - switch (baud) - { - case CAN_BR_2M: dat_brp = 1; dat_seg1 = 29; dat_seg2 = 10; dat_sjw = 10; break; - case CAN_BR_2M5: dat_brp = 1; dat_seg1 = 25; dat_seg2 = 6; dat_sjw = 6; break; - case CAN_BR_3M2: dat_brp = 1; dat_seg1 = 19; dat_seg2 = 5; dat_sjw = 5; break; - case CAN_BR_4M: dat_brp = 1; dat_seg1 = 14; dat_seg2 = 5; dat_sjw = 5; break; - case CAN_BR_5M: dat_brp = 1; dat_seg1 = 13; dat_seg2 = 2; dat_sjw = 2; break; + } else if (mode == CAN_FD_BRS) { + switch (baud) { + case CAN_BR_2M: dat_brp = 1; + dat_seg1 = 29; + dat_seg2 = 10; + dat_sjw = 10; + break; + case CAN_BR_2M5: dat_brp = 1; + dat_seg1 = 25; + dat_seg2 = 6; + dat_sjw = 6; + break; + case CAN_BR_3M2: dat_brp = 1; + dat_seg1 = 19; + dat_seg2 = 5; + dat_sjw = 5; + break; + case CAN_BR_4M: dat_brp = 1; + dat_seg1 = 14; + dat_seg2 = 5; + dat_sjw = 5; + break; + case CAN_BR_5M: dat_brp = 1; + dat_seg1 = 13; + dat_seg2 = 2; + dat_sjw = 2; + break; default: break; } - nom_brp = 1; nom_seg1 = 59; nom_seg2 = 20; nom_sjw = 20; // 数据域默认1M + nom_brp = 1; + nom_seg1 = 59; + nom_seg2 = 20; + nom_sjw = 20; // 数据域默认1M hfdcan->Init.FrameFormat = FDCAN_FRAME_FD_BRS; } @@ -296,8 +319,7 @@ void bsp_fdcan_set_baud(FDCAN_HandleTypeDef *hfdcan, uint8_t mode, uint8_t baud) hfdcan->Init.DataTimeSeg2 = dat_seg2; hfdcan->Init.DataSyncJumpWidth = dat_sjw; - if (HAL_FDCAN_Init(hfdcan) != HAL_OK) - { + if (HAL_FDCAN_Init(hfdcan) != HAL_OK) { Error_Handler(); } @@ -306,12 +328,9 @@ void bsp_fdcan_set_baud(FDCAN_HandleTypeDef *hfdcan, uint8_t mode, uint8_t baud) HAL_FDCAN_ConfigGlobalFilter(hfdcan, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE); // 重新激活通知 - if (hfdcan == &hfdcan1) - { + if (hfdcan == &hfdcan1) { HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); - } - else - { + } else { HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); } HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_BUS_OFF, 0); @@ -326,8 +345,7 @@ void bsp_fdcan_set_baud(FDCAN_HandleTypeDef *hfdcan, uint8_t mode, uint8_t baud) * @param _hcan FDCAN句柄 * @param fifox 传递给HAL_FDCAN_GetRxMessage()以从特定FIFO获取消息 */ -static void FDCANFIFOxCallback(FDCAN_HandleTypeDef *_hcan, uint32_t fifox) -{ +static void FDCANFIFOxCallback(FDCAN_HandleTypeDef *_hcan, uint32_t fifox) { FDCAN_RxHeaderTypeDef rxconf; uint8_t can_rx_buff[64]; @@ -335,15 +353,14 @@ static void FDCANFIFOxCallback(FDCAN_HandleTypeDef *_hcan, uint32_t fifox) { HAL_FDCAN_GetRxMessage(_hcan, fifox, &rxconf, can_rx_buff); // 从FIFO中获取数据 - for (size_t i = 0; i < idx; ++i) - { // 两者相等说明这是要找的实例 - if (_hcan == fdcan_instance[i]->can_handle && rxconf.Identifier == fdcan_instance[i]->rx_id) - { + for (size_t i = 0; i < idx; ++i) { + // 两者相等说明这是要找的实例 + if (_hcan == fdcan_instance[i]->can_handle && rxconf.Identifier == fdcan_instance[i]->rx_id) { if (fdcan_instance[i]->can_module_callback != NULL) // 回调函数不为空就调用 { - fdcan_instance[i]->rx_len = FDCANDlcToLen(rxconf.DataLength); // 保存接收到的数据长度 + fdcan_instance[i]->rx_len = FDCANDlcToLen(rxconf.DataLength); // 保存接收到的数据长度 memcpy(fdcan_instance[i]->rx_buff, can_rx_buff, fdcan_instance[i]->rx_len); // 消息拷贝到对应实例 - fdcan_instance[i]->can_module_callback(fdcan_instance[i]); // 触发回调进行数据解析和处理 + fdcan_instance[i]->can_module_callback(fdcan_instance[i]); // 触发回调进行数据解析和处理 } return; } @@ -363,10 +380,8 @@ static void FDCANFIFOxCallback(FDCAN_HandleTypeDef *_hcan, uint32_t fifox) * @param hfdcan FDCAN handle indicate which device the message in FIFO_0 comes from * @param RxFifo0ITs FIFO0中断标志 */ -void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) -{ - if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET) - { +void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { + if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET) { FDCANFIFOxCallback(hfdcan, FDCAN_RX_FIFO0); // 调用我们自己写的函数来处理消息 } } @@ -377,10 +392,8 @@ void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) * @param hfdcan FDCAN handle indicate which device the message in FIFO_1 comes from * @param RxFifo1ITs FIFO1中断标志 */ -void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) -{ - if ((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) != RESET) - { +void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) { + if ((RxFifo1ITs & FDCAN_IT_RX_FIFO1_NEW_MESSAGE) != RESET) { FDCANFIFOxCallback(hfdcan, FDCAN_RX_FIFO1); // 调用我们自己写的函数来处理消息 } } @@ -390,30 +403,22 @@ void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs) * @param hfdcan FDCAN句柄 * @param ErrorStatusITs 错误状态中断标志 */ -void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs) -{ - if (ErrorStatusITs & FDCAN_IR_BO) - { +void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs) { + if (ErrorStatusITs & FDCAN_IR_BO) { // 总线关闭恢复 CLEAR_BIT(hfdcan->Instance->CCCR, FDCAN_CCCR_INIT); hfdcan->ErrorCode = 0; // LOGWARNING("[bsp_fdcan] FDCAN Bus Off recovered"); } - if (ErrorStatusITs & FDCAN_IR_EP) - { + if (ErrorStatusITs & FDCAN_IR_EP) { // 错误被动状态处理,重新初始化FDCAN // LOGWARNING("[bsp_fdcan] FDCAN Error Passive, reinitializing..."); - if (hfdcan->Instance == FDCAN1) - { + if (hfdcan->Instance == FDCAN1) { MX_FDCAN1_Init(); - } - else if (hfdcan->Instance == FDCAN2) - { + } else if (hfdcan->Instance == FDCAN2) { MX_FDCAN2_Init(); - } - else if (hfdcan->Instance == FDCAN3) - { + } else if (hfdcan->Instance == FDCAN3) { MX_FDCAN3_Init(); } @@ -421,12 +426,9 @@ void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorSt HAL_FDCAN_Start(hfdcan); HAL_FDCAN_ConfigGlobalFilter(hfdcan, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE); - if (hfdcan == &hfdcan1) - { + if (hfdcan == &hfdcan1) { HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); - } - else - { + } else { HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0); } HAL_FDCAN_ActivateNotification(hfdcan, FDCAN_IT_BUS_OFF, 0); @@ -436,17 +438,14 @@ void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorSt } // 弱定义的接收回调函数,用户可重写 -__weak void fdcan1_rx_callback(void) -{ +__weak void fdcan1_rx_callback(void) { // 用户可重写 } -__weak void fdcan2_rx_callback(void) -{ +__weak void fdcan2_rx_callback(void) { // 用户可重写 } -__weak void fdcan3_rx_callback(void) -{ +__weak void fdcan3_rx_callback(void) { // 用户可重写 -} \ No newline at end of file +} diff --git a/User_Code/bsp/fdcan/bsp_fdcan.md b/User_Code/bsp/fdcan/bsp_fdcan.md index 6a0dbe6..4d7a9ce 100644 --- a/User_Code/bsp/fdcan/bsp_fdcan.md +++ b/User_Code/bsp/fdcan/bsp_fdcan.md @@ -1,10 +1,11 @@ # bsp_fdcan.c/bsp_fdcan.h -# 请注意使用FDCAN设备的时候务必保证总线只接入了2个终端电阻!开发板一般都有一个,6020电机、c620/c610电调、LK电机也都有终端电阻,注意把多于2个的全部断开(通过拨码) +> 请注意使用FDCAN设备的时候务必保证总线只接入了2个终端电阻!开发板一般都有一个,6020电机、c620/c610电调、LK电机也都有终端电阻,注意把多于2个的全部断开(通过拨码) ## 使用说明 -若你希望新增一个基于FDCAN的module,首先在该模块下应该有一个包含`FDCANInstance`指针的module结构体(或当功能简单的时候,可以是单独存在的`FDCANInstance`,但不推荐这样做)。 +若你希望新增一个基于FDCAN的module,首先在该模块下应该有一个包含`FDCANInstance`指针的module结构体(或当功能简单的时候,可以是单独存在的 +`FDCANInstance`,但不推荐这样做)。 ## 代码结构 @@ -64,7 +65,9 @@ typedef void (*fdcan_callback)(FDCANInstance *); - `MX_CAN_FILTER_CNT`是最大的FDCAN接收过滤器数量,三个FDCAN共享多个过滤器。当前为简单起见,每个过滤器只设置一组规则用于控制一个id的过滤。 - `DEVICE_CAN_CNT`是MCU拥有的FDCAN硬件数量(H7有3个)。 -- `FDCANInstance`是一个FDCAN实例。注意,FDCAN作为一个总线设备,一条总线上可以挂载多个设备,因此多个设备可以共享同一个FDCAN硬件。其成员变量包括发送id,发送邮箱,发送buff以及接收buff,还有接收id和接收协议解析回调函数。**FDCAN支持最大64字节的数据帧**,因此收发buff长度设置为64字节。 +- `FDCANInstance` + 是一个FDCAN实例。注意,FDCAN作为一个总线设备,一条总线上可以挂载多个设备,因此多个设备可以共享同一个FDCAN硬件。其成员变量包括发送id,发送邮箱,发送buff以及接收buff,还有接收id和接收协议解析回调函数。 + **FDCAN支持最大64字节的数据帧**,因此收发buff长度设置为64字节。 - `FDCAN_Init_Config_s`是用于初始化FDCAN实例的结构,在调用FDCAN实例的初始化函数时传入。 @@ -80,7 +83,8 @@ uint8_t FDCANTransmit(FDCANInstance *_instance, float timeout); void bsp_fdcan_set_baud(FDCAN_HandleTypeDef *hfdcan, uint8_t mode, uint8_t baud); ``` -`FDCANRegister`是用于初始化FDCAN实例的接口,module层的模块对象(也应当为一个结构体)内要包含一个`FDCANInstance`。调用时传入初始化配置的指针。`FDCANRegister`应当在module的初始化函数内被调用,推荐config采用以下的方式定义,更加直观明了: +`FDCANRegister`是用于初始化FDCAN实例的接口,module层的模块对象(也应当为一个结构体)内要包含一个`FDCANInstance` +。调用时传入初始化配置的指针。`FDCANRegister`应当在module的初始化函数内被调用,推荐config采用以下的方式定义,更加直观明了: ```c FDCAN_Init_Config_s config = { @@ -92,7 +96,8 @@ FDCAN_Init_Config_s config = { }; ``` -`FDCANTransmit()`是通过模块通过其拥有的FDCAN实例发送数据的接口,调用时传入对应的instance。在发送之前,应当给instance内的`tx_buff`赋值。 +`FDCANTransmit()`是通过模块通过其拥有的FDCAN实例发送数据的接口,调用时传入对应的instance。在发送之前,应当给instance内的 +`tx_buff`赋值。 `bsp_fdcan_set_baud()`用于设置FDCAN的波特率和模式(经典CAN或CAN FD)。 @@ -119,11 +124,14 @@ void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorSt - `FDCANServiceInit()`会被`FDCANRegister()`调用,对FDCAN外设进行硬件初始化并开启接收中断和消息提醒。 -- `FDCANAddFilter()`在每次使用`FDCANRegister()`的时候被调用,用于给当前注册的实例添加过滤器规则并设定处理对应`rx_id`的接收FIFO。过滤器的作用是减小FDCAN收发器的压力,只接收符合过滤器规则的报文(否则不会产生接收中断)。 +- `FDCANAddFilter()`在每次使用`FDCANRegister()`的时候被调用,用于给当前注册的实例添加过滤器规则并设定处理对应`rx_id` + 的接收FIFO。过滤器的作用是减小FDCAN收发器的压力,只接收符合过滤器规则的报文(否则不会产生接收中断)。 - `FDCANDlcToLen()`和`FDCANLenToDlc()`是FDCAN特有的数据长度码转换函数,用于在数据长度码和实际字节数之间转换。 -- `HAL_FDCAN_RxFifo0Callback()`和`HAL_FDCAN_RxFifo1Callback()`都是对HAL的FDCAN回调函数的重定义(原本的callback是`__weak`修饰的弱定义),当发生FIFO0或FIFO1有新消息到达的时候,对应的callback会被调用。`FDCANFIFOxCallback()`随后被前两者调用,并根据接收id和硬件中断来源(哪一个FDCAN硬件)调用对应的instance的回调函数进行协议解析。 +- `HAL_FDCAN_RxFifo0Callback()`和`HAL_FDCAN_RxFifo1Callback()`都是对HAL的FDCAN回调函数的重定义(原本的callback是`__weak` + 修饰的弱定义),当发生FIFO0或FIFO1有新消息到达的时候,对应的callback会被调用。`FDCANFIFOxCallback()` + 随后被前两者调用,并根据接收id和硬件中断来源(哪一个FDCAN硬件)调用对应的instance的回调函数进行协议解析。 - `HAL_FDCAN_ErrorStatusCallback()`处理FDCAN的错误状态,包括总线关闭和错误被动状态的恢复。 @@ -148,9 +156,12 @@ __weak void fdcan3_rx_callback(void); - CAN FD模式下支持最高64字节的数据帧 - 支持更高的通信速率(最高5Mbps) -3. **发送超时**:由于FDCAN总线自带发送检测,如果总线上没有挂载目标设备,那么发送FIFO会被占满而无法发送。在`FDCANTransmit()`中会对发送FIFO是否已满进行检查。当超出`timeout`之后函数会返回零,说明发送失败。 +3. **发送超时**:由于FDCAN总线自带发送检测,如果总线上没有挂载目标设备,那么发送FIFO会被占满而无法发送。在`FDCANTransmit()` + 中会对发送FIFO是否已满进行检查。当超出`timeout`之后函数会返回零,说明发送失败。 -4. **任务阻塞**:由于卡在`while(1)`处不断检查FIFO是否空闲,调用`FDCANTransmit()`的任务可能无法按时挂起,导致任务定时不精确。建议在没有连接FDCAN进行调试时,按需注释掉有关FDCAN发送的代码部分,或设定一个较小的`timeout`值,防止对其他需要精确定时的任务产生影响。 +4. **任务阻塞**:由于卡在`while(1)`处不断检查FIFO是否空闲,调用`FDCANTransmit()` + 的任务可能无法按时挂起,导致任务定时不精确。建议在没有连接FDCAN进行调试时,按需注释掉有关FDCAN发送的代码部分,或设定一个较小的 + `timeout`值,防止对其他需要精确定时的任务产生影响。 5. **模式选择**:使用`bsp_fdcan_set_baud()`函数可以在经典CAN模式和CAN FD模式之间切换,注意不同模式支持的波特率范围不同。 diff --git a/User_Code/bsp/spi/bsp_spi.c b/User_Code/bsp/spi/bsp_spi.c index 75f9017..b73a284 100644 --- a/User_Code/bsp/spi/bsp_spi.c +++ b/User_Code/bsp/spi/bsp_spi.c @@ -1,5 +1,131 @@ -// -// Created by tuxmonkey on 2025/10/28. -// +#include "bsp_spi.h" +#include "memory.h" +#include "stdlib.h" -#include "bsp_spi.h" \ No newline at end of file +/* 所有的spi instance保存于此,用于callback时判断中断来源*/ +static SPIInstance *spi_instance[SPI_DEVICE_CNT] = {NULL}; +static uint8_t idx = 0; // 配合中断以及初始化 + +SPIInstance *SPIRegister(SPI_Init_Config_s *conf) { + if (idx >= MX_SPI_BUS_SLAVE_CNT) // 超过最大实例数 + while (1); + SPIInstance *instance = (SPIInstance *) malloc(sizeof(SPIInstance)); + memset(instance, 0, sizeof(SPIInstance)); + + instance->spi_handle = conf->spi_handle; + instance->GPIOx = conf->GPIOx; + instance->cs_pin = conf->cs_pin; + instance->spi_work_mode = conf->spi_work_mode; + instance->callback = conf->callback; + instance->id = conf->id; + + spi_instance[idx++] = instance; + return instance; +} + +void SPITransmit(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len) { + // 拉低片选,开始传输(选中从机) + HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_RESET); + switch (spi_ins->spi_work_mode) { + case SPI_DMA_MODE: + HAL_SPI_Transmit_DMA(spi_ins->spi_handle, ptr_data, len); + break; + case SPI_IT_MODE: + HAL_SPI_Transmit_IT(spi_ins->spi_handle, ptr_data, len); + break; + case SPI_BLOCK_MODE: + HAL_SPI_Transmit(spi_ins->spi_handle, ptr_data, len, 1000); // 默认50ms超时 + // 阻塞模式不会调用回调函数,传输完成后直接拉高片选结束 + HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_SET); + break; + default: + while (1); // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况 + break; + } +} + +void SPIRecv(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len) { + // 用于稍后回调使用 + spi_ins->rx_size = len; + spi_ins->rx_buffer = ptr_data; + // 拉低片选,开始传输 + HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_RESET); + switch (spi_ins->spi_work_mode) { + case SPI_DMA_MODE: + HAL_SPI_Receive_DMA(spi_ins->spi_handle, ptr_data, len); + break; + case SPI_IT_MODE: + HAL_SPI_Receive_IT(spi_ins->spi_handle, ptr_data, len); + break; + case SPI_BLOCK_MODE: + HAL_SPI_Receive(spi_ins->spi_handle, ptr_data, len, 1000); + // 阻塞模式不会调用回调函数,传输完成后直接拉高片选结束 + HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_SET); + break; + default: + while (1); // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况 + break; + } +} + +void SPITransRecv(SPIInstance *spi_ins, uint8_t *ptr_data_rx, uint8_t *ptr_data_tx, uint8_t len) { + // 用于稍后回调使用,请保证ptr_data_rx在回调函数被调用之前仍然在作用域内,否则析构之后的行为是未定义的!!! + spi_ins->rx_size = len; + spi_ins->rx_buffer = ptr_data_rx; + // 拉低片选,开始传输 + HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_RESET); + switch (spi_ins->spi_work_mode) { + case SPI_DMA_MODE: + HAL_SPI_TransmitReceive_DMA(spi_ins->spi_handle, ptr_data_tx, ptr_data_rx, len); + break; + case SPI_IT_MODE: + HAL_SPI_TransmitReceive_IT(spi_ins->spi_handle, ptr_data_tx, ptr_data_rx, len); + break; + case SPI_BLOCK_MODE: + HAL_SPI_TransmitReceive(spi_ins->spi_handle, ptr_data_tx, ptr_data_rx, len, 1000); // 默认50ms超时 + // 阻塞模式不会调用回调函数,传输完成后直接拉高片选结束 + HAL_GPIO_WritePin(spi_ins->GPIOx, spi_ins->cs_pin, GPIO_PIN_SET); + break; + default: + while (1); // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况 + break; + } +} + +void SPISetMode(SPIInstance *spi_ins, SPI_TXRX_MODE_e spi_mode) { + if (spi_mode != SPI_DMA_MODE && spi_mode != SPI_IT_MODE && spi_mode != SPI_BLOCK_MODE) + while (1); // error mode! 请查看是否正确设置模式,或出现指针越界导致模式被异常修改的情况 + + if (spi_ins->spi_work_mode != spi_mode) { + spi_ins->spi_work_mode = spi_mode; + } +} + +/** + * @brief 当SPI接收完成,将会调用此回调函数,可以进行协议解析或其他必须的数据处理等 + * + * @param hspi spi handle + */ +void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { + for (size_t i = 0; i < idx; i++) { + // 如果是当前spi硬件发出的complete,且cs_pin为低电平(说明正在传输),则尝试调用回调函数 + if (spi_instance[i]->spi_handle == hspi && // 显然同一时间一条总线只能有一个从机在接收数据 + HAL_GPIO_ReadPin(spi_instance[i]->GPIOx, spi_instance[i]->cs_pin) == GPIO_PIN_RESET) { + // 先拉高片选,结束传输,在判断是否有回调函数,如果有则调用回调函数 + HAL_GPIO_WritePin(spi_instance[i]->GPIOx, spi_instance[i]->cs_pin, GPIO_PIN_SET); + // @todo 后续添加holdon模式,由用户自行决定何时释放片选,允许进行连续传输 + if (spi_instance[i]->callback != NULL) // 回调函数不为空, 则调用回调函数 + spi_instance[i]->callback(spi_instance[i]); + return; + } + } +} + +/** + * @brief 和RxCpltCallback共用解析即可,这里只是形式上封装一下,不用重复写 + * 这是对HAL库的__weak函数的重写,传输使用IT或DMA模式,在传输完成时会调用此函数 + * @param hspi spi handle + */ +void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { + HAL_SPI_RxCpltCallback(hspi); // 直接调用接收完成的回调函数 +} diff --git a/User_Code/bsp/spi/bsp_spi.h b/User_Code/bsp/spi/bsp_spi.h index 63404d8..eaf5398 100644 --- a/User_Code/bsp/spi/bsp_spi.h +++ b/User_Code/bsp/spi/bsp_spi.h @@ -1,8 +1,94 @@ -// -// Created by tuxmonkey on 2025/10/28. -// +#include "spi.h" +#include "stdint.h" +#include "gpio.h" -#ifndef TRONONEH7_SCAFFOLD_BSP_SPI_H -#define TRONONEH7_SCAFFOLD_BSP_SPI_H +/* 根据开发板引出的spi引脚以及CubeMX中的初始化配置设定 */ +#define SPI_DEVICE_CNT 2 // C型开发板引出两路spi,分别连接BMI088/作为扩展IO在8pin牛角座引出 +#define MX_SPI_BUS_SLAVE_CNT 4 // 单个spi总线上挂载的从机数目 -#endif //TRONONEH7_SCAFFOLD_BSP_SPI_H \ No newline at end of file +/* spi transmit recv mode enumerate*/ +typedef enum { + SPI_BLOCK_MODE = 0, // 默认使用阻塞模式 + SPI_IT_MODE, + SPI_DMA_MODE, +} SPI_TXRX_MODE_e; + +/* SPI实例结构体定义 */ +typedef struct spi_ins_temp { + SPI_HandleTypeDef *spi_handle; // SPI外设handle + GPIO_TypeDef *GPIOx; // 片选信号对应的GPIO,如GPIOA,GPIOB等等 + uint16_t cs_pin; // 片选信号对应的引脚号,GPIO_PIN_1,GPIO_PIN_2等等 + + SPI_TXRX_MODE_e spi_work_mode; // 传输工作模式 + uint8_t rx_size; // 本次接收的数据长度 + uint8_t *rx_buffer; // 本次接收的数据缓冲区 + + void (*callback)(struct spi_ins_temp *); // 接收回调函数 + void *id; // 模块指针 +} SPIInstance; + +/* 接收回调函数定义,包含SPI的module按照此格式构建回调函数 */ +typedef void (*spi_rx_callback)(SPIInstance *); + +// @todo: 这里可以将GPIO_TypeDef *GPIOx; uint16_t cs_pin合并为bsp_gpio以简化代码实现 +/* SPI初始化配置,其实基本和SPIIstance一模一样,为了代码风格统一因此再次定义 */ +typedef struct { + SPI_HandleTypeDef *spi_handle; // SPI外设handle + GPIO_TypeDef *GPIOx; // 片选信号对应的GPIO,如GPIOA,GPIOB等等 + uint16_t cs_pin; // 片选信号对应的引脚号,GPIO_PIN_1,GPIO_PIN_2等等 + + SPI_TXRX_MODE_e spi_work_mode; // 传输工作模式 + + spi_rx_callback callback; // 接收回调函数 + void *id; // 模块指针 +} SPI_Init_Config_s; + +/** + * @brief 注册一个spi instance + * + * @param conf 传入spi配置 + * @return SPIInstance* 返回一个spi实例指针,之后通过该指针操作spi外设 + */ +SPIInstance *SPIRegister(SPI_Init_Config_s *conf); + +/** + * @brief 通过spi向对应从机发送数据 + * @todo 后续加入阻塞模式下的timeout参数 + * + * @param spi_ins spi实例指针 + * @param ptr_data 要发送的数据 + * @param len 待发送的数据长度 + */ +void SPITransmit(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len); + +/** + * @brief 通过spi从从机获取数据 + * @attention 特别注意:请保证ptr_data在回调函数被调用之前仍然在作用域内,否则析构之后的行为是未定义的!!! + * + * @param spi_ins spi实例指针 + * @param ptr_data 接受数据buffer的首地址 + * @param len 待接收的长度 + */ +void SPIRecv(SPIInstance *spi_ins, uint8_t *ptr_data, uint8_t len); + +/** + * @brief 通过spi利用移位寄存器同时收发数据 + * @todo 后续加入阻塞模式下的timeout参数 + * @attention 特别注意:请保证ptr_data_rx在回调函数被调用之前仍然在作用域内,否则析构之后的行为是未定义的!!! + * + * @param spi_ins spi实例指针 + * @param ptr_data_rx 接收数据地址 + * @param ptr_data_tx 发送数据地址 + * @param len 接收&发送的长度 + */ +void SPITransRecv(SPIInstance *spi_ins, uint8_t *ptr_data_rx, uint8_t *ptr_data_tx, uint8_t len); + +/** + * @brief 设定spi收发的工作模式 + * + * @param spi_ins spi实例指针 + * @param spi_mode 工作模式,包括阻塞模式(block),中断模式(IT),DMA模式.详见SPI_TXRX_MODE_e的定义 + * + * @todo 是否直接将mode作为transmit/recv的参数,而不是作为spi实例的属性?两者各有优劣 + */ +void SPISetMode(SPIInstance *spi_ins, SPI_TXRX_MODE_e spi_mode); diff --git a/User_Code/bsp/spi/bsp_spi.md b/User_Code/bsp/spi/bsp_spi.md new file mode 100644 index 0000000..e82d6f8 --- /dev/null +++ b/User_Code/bsp/spi/bsp_spi.md @@ -0,0 +1,11 @@ +# bsp_spi.c/bsp_spi.h + +> 预计增加模拟spi + +初始化传入参数中的GPIOx(GPIOA,GPIOB,...)和cs_pin(GPIO_PIN_1,GPIO_PIN_2, +...)都是HAL库内建的宏,在CubeMX初始化的时候若有给gpio分配标签则填入对应名字即可,否则填入原本的宏。 + +注意,如果你没有在CubeMX中为spi分配dma通道,请不要使用dma模式 + +(后续添加安全检查,通过判断hspi的dma handler是否为空来选择模式,如果为空,则自动将DMA转为IT模式以继续传输,并通过log warning +提醒用户) \ No newline at end of file