add bsp_spi

This commit is contained in:
TuxMonkey
2025-11-03 22:08:14 +08:00
parent 38ecee9603
commit 7a113a32dd
6 changed files with 385 additions and 150 deletions

View File

@@ -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) {
// 用户可重写
}
}

View File

@@ -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模式之间切换注意不同模式支持的波特率范围不同。

View File

@@ -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"
/* 所有的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); // 直接调用接收完成的回调函数
}

View File

@@ -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
/* 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);

View File

@@ -0,0 +1,11 @@
# bsp_spi.c/bsp_spi.h
> 预计增加模拟spi
初始化传入参数中的GPIOxGPIOAGPIOB...和cs_pinGPIO_PIN_1,GPIO_PIN_2,
...都是HAL库内建的宏在CubeMX初始化的时候若有给gpio分配标签则填入对应名字即可否则填入原本的宏。
注意如果你没有在CubeMX中为spi分配dma通道请不要使用dma模式
后续添加安全检查通过判断hspi的dma handler是否为空来选择模式如果为空则自动将DMA转为IT模式以继续传输并通过log warning
提醒用户)