CanFestival移植到STM32G4

发布于:2025-06-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、准备工作

1、获取Canfestival源码
2、Python下载
3、wxPython下载
4、CanFestival字典生成
5、安装参考
Python2.7.15及wxPython2.8百度云盘下载地址:https://pan.baidu.com/s/1bRS403m4B31m4ovSJ-_HwA
提取码:38sn

二、软件配置

FDCAN 500K@2M配置
在这里插入图片描述

    /*
     * Bit timing & sampling
     * Tq = (BRP+1)/Fcan if DIV8 = 0
     * Tq = 8*(BRP+1)/Fcan if DIV8 = 1
     * TSync = 1.Tq
     * TSeg1 = (TSEG1+1)*Tq                >= 3Tq
     * TSeg2 = (TSEG2+1)*Tq                >= 2Tq
     * Bit Time = TSync + TSeg1 + TSeg2    >= 8Tq
     *
     * Resynchronization:
     *
     * Tsjw = (SJW + 1)*Tq
     * TSeg1 >= Tsjw + Tprop
     * TSeg2 >= Tsjw
     */

在这里插入图片描述
可以通过 KVASER Bit Timing Calculator for CANFD 这个网站在线计算.
CANFD关键代码:

FDCAN_FilterTypeDef sFilterConfig2;
FDCAN_RxHeaderTypeDef RxHeader2;
FDCAN_TxHeaderTypeDef TxHeader2;

void fdcan2_config(void)
{
    sFilterConfig2.IdType = FDCAN_STANDARD_ID;
    sFilterConfig2.FilterIndex = 0;
    sFilterConfig2.FilterType = FDCAN_FILTER_RANGE;
    sFilterConfig2.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    sFilterConfig2.FilterID1 = 0x00;
    sFilterConfig2.FilterID2 = 0x7FF;
    if (HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig2) != HAL_OK)
    {
        Error_Handler();
    }

    sFilterConfig2.IdType = FDCAN_EXTENDED_ID;
    sFilterConfig2.FilterIndex = 0;
    sFilterConfig2.FilterType = FDCAN_FILTER_RANGE;
    sFilterConfig2.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    sFilterConfig2.FilterID1 = 0x00;
    sFilterConfig2.FilterID2 = 0x1FFFFFFF;
    if (HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig2) != HAL_OK)
    {
        Error_Handler();
    }

    /* Configure global filter on both FDCAN instances:
  Filter all remote frames with STD and EXT ID
  Reject non matching frames with STD ID and EXT ID */
    if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
    {
        Error_Handler();
    }

    /* Activate Rx FIFO 0 new message notification on both FDCAN instances */
    if (HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
    {
        Error_Handler();
    }

    if (HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_BUS_OFF, 0) != HAL_OK)
    {
        Error_Handler();
    }

    /* Configure and enable Tx Delay Compensation, required for BRS mode.
        TdcOffset default recommended value: DataTimeSeg1 * DataPrescaler
        TdcFilter default recommended value: 0 */
    HAL_FDCAN_ConfigTxDelayCompensation(&hfdcan2, hfdcan2.Init.DataPrescaler * hfdcan2.Init.DataTimeSeg1, 0);
    HAL_FDCAN_EnableTxDelayCompensation(&hfdcan2);

    HAL_FDCAN_Start(&hfdcan2);
}


void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs)
{
    if(hfdcan->Instance == FDCAN2) {
        MX_FDCAN2_Init();
        fdcan2_config();
    } 
}

//RxHeader1.DataLength => can_dlc
//0x00000 => 0 
//0x10000 => 1 
//0x20000 => 2 
//0x30000 => 3 
//0x40000 => 4 
//0x50000 => 5 
//0x60000 => 6 
//0x70000 => 7 
//0x80000 => 8 
//0x90000 => 12
//0xA0000 => 16
//0xB0000 => 20
//0xC0000 => 24
//0xD0000 => 32
//0xE0000 => 48
//0xF0000 => 64
uint8_t dlc2len[]={0,1,2,3,4,5,6,7,8,12,16,20,24,32,48,64};
uint8_t RxData2[100];
uint8_t TxData2[64];
uint8_t can_dlc2len(uint32_t RxHeader_DataLength)
{
    return dlc2len[RxHeader_DataLength>>16];
}

uint8_t cnt = 0;
uint8_t brs[] = {'-', 'B'};
uint8_t esi[] = {'-', 'E'};
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
    if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != 0) {      
        HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader2, RxData2);
        if (hfdcan->Instance == FDCAN2) { 
            usb_printf("fdcan2, ");
        } 
    }
    usb_printf("0x%8X, %02d, %c, %c:",RxHeader2.Identifier, 
               can_dlc2len(RxHeader2.DataLength), 
               brs[RxHeader2.BitRateSwitch>>20 & 0x1],
                                  esi[RxHeader2.ErrorStateIndicator>>31 & 0x1]);
    for(cnt = 0; cnt < can_dlc2len(RxHeader2.DataLength); cnt++) {
      usb_printf(" %02X", RxData2[cnt]);
    }
    usb_printf("\n\r");
      
}


FDCAN_SendFailTypeDef fdcan2_send_fail = {0};
void fdcan2_transmit(uint32_t can_id, uint32_t DataLength, uint8_t tx_data[])
{
  TxHeader2.Identifier = can_id;
  TxHeader2.IdType = FDCAN_EXTENDED_ID;
  if(can_id < 0x800) {  //exactly not right
    TxHeader2.IdType = FDCAN_STANDARD_ID;
  }
  TxHeader2.TxFrameType = FDCAN_DATA_FRAME;
  TxHeader2.DataLength = DataLength;
  TxHeader2.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
  TxHeader2.BitRateSwitch = FDCAN_BRS_ON;
  TxHeader2.FDFormat = FDCAN_FD_CAN;
  TxHeader2.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
  TxHeader2.MessageMarker = 0;	//marker++;	//Tx Event FIFO Use
  if(HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &TxHeader2, tx_data) != HAL_OK) {
    fdcan2_send_fail.flag = 1;
    memcpy(&fdcan2_send_fail.TxHeader, &TxHeader2, sizeof(FDCAN_TxHeaderTypeDef));
    memcpy(fdcan2_send_fail.TxData, tx_data, can_dlc2len(DataLength));
  } 
}

  uint32_t count = 0;
  uint32_t cnt_100us = 0;
  uint32_t cnt_500us = 0;
  uint8_t tim4_flag = 0;
void canfd_test(void)
{
	  for(uint8_t i = 0; i < 64; i++) {
        TxData2[i] = i;
    }
  if(count < 1000) {
      TxData2[0] = count >> 8 & 0xFF;
      TxData2[1] = count & 0xFF;

      ++cnt_100us;
      cnt_500us = cnt_100us / 5;
      if(cnt_500us && (cnt_100us%5==0) ) {
        switch(cnt_500us) {
          case 1: fdcan2_transmit(0x123, FDCAN_DLC_BYTES_64, TxData2); 
                  fdcan2_transmit(0x124, FDCAN_DLC_BYTES_64, TxData2);
                  fdcan2_transmit(0x125, FDCAN_DLC_BYTES_64, TxData2); break;
          case 4: fdcan2_transmit(0x12345678, FDCAN_DLC_BYTES_64, TxData2); break;
          case 5: fdcan2_transmit(0x12345679, FDCAN_DLC_BYTES_64, TxData2); break;
          case 6: fdcan2_transmit(0x1234567A, FDCAN_DLC_BYTES_64, TxData2); break;
          case 7: /* next send */ break;
          case 8: break;
          case 20: ++count; cnt_100us = 0; break; //10ms
        }
      } else {  //fail retransmission once

        if(fdcan2_send_fail.flag) {
          fdcan2_transmit(fdcan2_send_fail.TxHeader.Identifier, 
                          fdcan2_send_fail.TxHeader.DataLength,
                          fdcan2_send_fail.TxData);
          fdcan2_send_fail.flag = 0;
        }
      }
    }

}

通讯可收发即可。

三、移植CanFestival

步骤一
在新建好的工程目录下新建文件夹Canopen,再在Canopen下新建文件夹driver、inc和src,再在inc文件夹下面新建stm32文件夹(我这里主要以移植到stm32为例说明,如果是移植到VC或其他平台下,这里也可以命名为其他名字,如vc)。
步骤二
将CanFestival-3-10\src目录下的dcf.c、emcy.c、lifegrd.c、lss.c、nmtMaster.c、nmtSlave.c、objacces.c、pdo.c、sdo.c、states.c、sync.c、timer.c共12个文件拷贝到CanFestival\src目录下;
将CanFestival-3-10\include目录下的所有.h文件共19个文件全部拷贝到CanFestival\inc目录下,
再把CanFestival-3-10\examples\AVR\Slave目录下的ObjDict.h文件拷贝过来,一共20个;
将CanFestival-3-10\include\AVR目录下的applicfg.h、canfestival.h、config.h、timerscfg.h共4个头文件拷贝到canfestival\inc\stm32目录下;
将CanFestival-3-10\objdictgen\MySlave目录下的slave.c、slave.h拷贝到canfestival\driver目录下,并在该目录下新建stm32_canfestival.c文件。(这里的MySlave是自己新建的,用于保存通过对象字典编辑工具生成的eds等文件)
步骤三
将CanFestival\src目录下的所有.c文件添加到工程;将canfestival\driver目录下的stm32_canfestival.c文件添加到工程;
如果实现的是从设备,再将canfestival\driver目录下的TestSlave.c文件添加到工程,如果实现的是主设备,则将TestMaster.c文件添加到工程;
步骤四
将文件目录canfestival\inc、canfestival\inc\stm32、canfestival\driver等路径添加到工程包含路径。
步骤五:
在stm32_canfestival.c中包含头文件#include “canfestival.h”,并定义如下函数:

void setTimer(TIMEVAL value)
{
}
TIMEVAL getElapsedTime(void)
{
  return 1;
}
unsigned char canSend(CAN_PORT notused, Message *m)
{
  return 1;
}

可以先定义一个空函数,等到编译都通过了之后,再往里面添加内容,这几个函数都是定义来供canfestival源码调用的,如果找不到这几个函数编译就会报错。
步骤六:
通过以上几步,所有的文件都弄齐了,但是编译一定会出现报错,注释或删除掉config.h文件中的如下几行就能编译通过:

include <inttypes.h>
#include <avr\io.h>
#include <avr\interrupt.h>
#include <avr/pgmspace.h>
#include <avr\sleep.h>
#include <avr\wdt.h>

如果还有其他报错,那有可能是因为不同源码版本、不同平台、不同人遇到的错误也会不相同,这里的过程只能做一定的参考,不一定完全相同,需要根据编译出错提示来进行修改对应地方,一般都是有些函数没声明或者某个头文件没有包含或者包含了一些不必要的头文件而该文件不存在或者是一些变量类型不符合需定义之类的。
在这里插入图片描述
如果提示:

error:  #3092: anonymous unions are only supported in --gnu mode, or when enabled with #pragma anon_unions

解决方法:
在这里插入图片描述
勾选GUN extensions。
步骤七
解决了所有的编译错误后,接下来实现刚才定义的3个空函数:

/**
  * @brief  Set timer for nex alarm
  * @param  value: timer value
  * @retval None
  */
void setTimer(TIMEVAL value)
{
    // 获取当前定时器值
    UNS32 timer = Bsp_Canopen_GetTimer();

    // 计算已经过去的时间
    elapsed_time += timer - last_counter_val;
    // 设置下一次定时器值
    last_counter_val = CANOPEN_TIM_PERIOD - value;

    // 设置定时器
    Bsp_Canopen_SetTimer(last_counter_val);
}


/**
  * @brief  Return the elapsed time to tell the Stack how much time is spent since the last call.
  * @param  None
  * @retval The elapsed time
  */
TIMEVAL getElapsedTime(void)
{
    uint32_t timer = Bsp_Canopen_GetTimer(); // Copy the value of the running timer
    if (timer < last_counter_val)
        timer += CANOPEN_TIM_PERIOD;
    TIMEVAL elapsed = timer - last_counter_val + elapsed_time;
    return elapsed;
}

/**
  * @brief  Can send
  * @param  notused: not used
  *         m: message point
  * @retval 0:successful -1:failed
  */
unsigned char canSend(CAN_PORT notused, Message *m)
{
    // 调用fdcan2_transmit函数发送消息
    return fdcan1_transmit(m->cob_id, m->len, m->data);
}



void CAN_DispatchRcv_Cbk(FDCAN_ReceivedFailTypeDef *RxMsg) {
    // 定义一个静态变量msg,用于存储接收到的消息
    static Message msg;

    // 如果接收到的消息标志位为1,则返回
    if (RxMsg->flag) {
        return; 
    }

    // 如果接收到的消息是标准ID,则将消息ID赋值给msg.cob_id
    if (RxMsg->RxHeader.IdType == FDCAN_STANDARD_ID) {
        msg.cob_id = RxMsg->RxHeader.Identifier & 0x7FF; 
    } else {
        // 如果接收到的消息是扩展ID,则将消息ID赋值给msg.cob_id,并将最高位设置为1
        msg.cob_id = RxMsg->RxHeader.Identifier | 0x80000000; 
    }

    // 将消息类型设置为数据帧
    msg.rtr = 0;

    // 将接收到的消息长度赋值给msg.len
    //msg.len = (UNS8)can_dlc2len(RxMsg->RxHeader.DataLength);
    msg.len = (UNS8)RxMsg->RxHeader.DataLength;

    // 将接收到的消息数据赋值给msg.data
    memcpy(msg.data, RxMsg->RxData, msg.len);

    // 禁用TIM4中断
    // HAL_NVIC_DisableIRQ(TIM4_IRQn);
    // 调用canDispatch函数,将接收到的消息传递给slave_Data
    canDispatch(&slave_Data, &msg);
    // 使能TIM4中断
    // HAL_NVIC_EnableIRQ(TIM4_IRQn);
}


/**
  * @brief  Timer ISR
  * @param  None
  * @retval None
  */
void TIMx_Dispatch_Cbk(void)
{
  last_counter_val = 0;
  elapsed_time = 0;
  
  TimeDispatch();
}
	

void CANopen_App_Init(CO_Data *d)
{	
	setNodeId(d, (UNS8)CAN_NodeID);
	setState(d, Initialisation);
	setState(d, Pre_operational);
    setState(d, Operational);
}

其中:

#define CANOPEN_TIM_PERIOD  0xffff

extern void canfd_test(void);
extern void TIMx_Dispatch_Cbk(void);
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 
{
    if(htim->Instance == TIM4) {
        TIMx_Dispatch_Cbk();
    }
}

/**
  * @brief  
	* @param  None
  * @retval void
  */
inline uint32_t Bsp_Canopen_GetTimer(void)
{
	return __HAL_TIM_GET_COUNTER(&htim4);
}

/**
  * @brief  
	* @param  None
  * @retval void
  */
inline void Bsp_Canopen_SetTimer(uint32_t value)
{
  __HAL_TIM_SET_COUNTER(&htim4, value);
}

步骤八
完善canfd收发函数

typedef struct {
  uint8_t flag;
  FDCAN_TxHeaderTypeDef TxHeader;
  uint8_t TxData[64];
} FDCAN_SendFailTypeDef;

typedef struct {
  uint8_t flag;
  FDCAN_RxHeaderTypeDef RxHeader;
  uint8_t RxData[64];
} FDCAN_ReceivedFailTypeDef;

FDCAN_FilterTypeDef sFilterConfig1;
FDCAN_RxHeaderTypeDef RxHeader1;
FDCAN_TxHeaderTypeDef TxHeader1;

void fdcan1_config(void)
{
  sFilterConfig1.IdType = FDCAN_STANDARD_ID;
  sFilterConfig1.FilterIndex = 0;
  sFilterConfig1.FilterType = FDCAN_FILTER_RANGE;
  sFilterConfig1.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
  sFilterConfig1.FilterID1 = 0x00;
  sFilterConfig1.FilterID2 = 0x7FF;
  if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig1) != HAL_OK)
  {
    Error_Handler();
  }

  sFilterConfig1.IdType = FDCAN_EXTENDED_ID;
  sFilterConfig1.FilterIndex = 0;
  sFilterConfig1.FilterType = FDCAN_FILTER_RANGE;
  sFilterConfig1.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
  sFilterConfig1.FilterID1 = 0x00;
  sFilterConfig1.FilterID2 = 0x1FFFFFFF;
  if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig1) != HAL_OK)
  {
    Error_Handler();
  }

  /* Configure global filter on both FDCAN instances:
  Filter all remote frames with STD and EXT ID
  Reject non matching frames with STD ID and EXT ID */
  if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
  {
    Error_Handler();
  }

  /* Activate Rx FIFO 0 new message notification on both FDCAN instances */
  if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_BUS_OFF, 0) != HAL_OK)
  {
    Error_Handler();
  }

  /* Configure and enable Tx Delay Compensation, required for BRS mode.
        TdcOffset default recommended value: DataTimeSeg1 * DataPrescaler
        TdcFilter default recommended value: 0 */
  HAL_FDCAN_ConfigTxDelayCompensation(&hfdcan1, hfdcan1.Init.DataPrescaler * hfdcan1.Init.DataTimeSeg1, 0);
  HAL_FDCAN_EnableTxDelayCompensation(&hfdcan1);

  HAL_FDCAN_Start(&hfdcan1);
}


void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs)
{
  if(hfdcan->Instance == FDCAN1) {
    MX_FDCAN1_Init();
    fdcan1_config();
  } 
}

//RxHeader1.DataLength => can_dlc
//0x00000 => 0 
//0x10000 => 1 
//0x20000 => 2 
//0x30000 => 3 
//0x40000 => 4 
//0x50000 => 5 
//0x60000 => 6 
//0x70000 => 7 
//0x80000 => 8 
//0x90000 => 12
//0xA0000 => 16
//0xB0000 => 20
//0xC0000 => 24
//0xD0000 => 32
//0xE0000 => 48
//0xF0000 => 64
uint8_t dlc2len[]={0,1,2,3,4,5,6,7,8,12,16,20,24,32,48,64};
uint8_t RxData1[100];
uint8_t TxData1[64];
uint8_t can_dlc2len(uint16_t data_length) {
    // 提取 DLC 的 4 位(例如:DLC 位于位 16-19)
    uint8_t dlc = (data_length >> 16) & 0x0F;  // 确保 dlc 在 0-15 范围内
    return (dlc < 16) ? dlc2len[dlc] : 0;      // 边界检查
}

uint8_t cnt = 0;
uint8_t brs[] = {'-', 'B'};
uint8_t esi[] = {'-', 'E'};
extern void CAN_DispatchRcv_Cbk(FDCAN_ReceivedFailTypeDef *RxMsg) ;
// 接收回调函数
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) {
    if (hfdcan->Instance == FDCAN1 && (RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE)) {
        FDCAN_RxHeaderTypeDef RxHeader1;
        uint8_t RxData1[64];
        FDCAN_ReceivedFailTypeDef RxMsg = {0};

        if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader1, RxData1) != HAL_OK) {
            Error_Handler();  // 自定义错误处理
            return;
        }

        // 提取数据长度
        //uint8_t data_len = can_dlc2len(RxHeader1.DataLength);
		uint8_t data_len = RxHeader1.DataLength;
        if (data_len > 64) {
            return;  // 无效长度处理
        }

        // 填充接收消息结构体
        RxMsg.RxHeader.Identifier = RxHeader1.Identifier;
        RxMsg.RxHeader.DataLength = data_len;
        RxMsg.RxHeader.BitRateSwitch = (RxHeader1.BitRateSwitch) ? 1 : 0;
        RxMsg.RxHeader.ErrorStateIndicator = (RxHeader1.ErrorStateIndicator) ? 1 : 0;
        memcpy(RxMsg.RxData, RxData1, data_len);

        // 调试输出
        #ifdef CAN_DEBUG
            LogCanFrame(&RxMsg);  // 封装调试函数
        #endif

        // 分发消息
        CAN_DispatchRcv_Cbk(&RxMsg);
    }
}


FDCAN_SendFailTypeDef fdcan1_send_fail = {0};
uint8_t fdcan1_transmit(uint32_t can_id, uint32_t DataLength, uint8_t tx_data[])
{
	// 定义一个FDCAN_TxHeaderTypeDef类型的变量TxHeader1,并初始化为0
	FDCAN_TxHeaderTypeDef TxHeader1={0};
	
  // 判断can_id的高位是否为1,如果是,则表示为扩展ID,否则为标准ID
  if ((can_id & 0x80000000) == 0) { 
      // 设置为标准ID
      TxHeader1.IdType = FDCAN_STANDARD_ID;
      // 将can_id的低11位赋值给TxHeader1.Identifier
      can_id &= 0x7FF;
  } else {                       
      // 设置为扩展ID
      TxHeader1.IdType = FDCAN_EXTENDED_ID;
      // 将can_id的低29位赋值给TxHeader1.Identifier
      can_id &= 0x1FFFFFFF;
  }
  // 设置标识符
  TxHeader1.Identifier = can_id;
  // 设置帧类型为数据帧
  TxHeader1.TxFrameType = FDCAN_DATA_FRAME;
  // 设置数据长度
  TxHeader1.DataLength = DataLength;
  // 设置错误状态指示器为活动状态
  TxHeader1.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
  // 设置比特率切换为开启
  TxHeader1.BitRateSwitch = FDCAN_BRS_ON;
  // 设置为FD格式
  TxHeader1.FDFormat = FDCAN_FD_CAN;
  // 设置为不使用Tx Event FIFO
  TxHeader1.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
  // 设置消息标记为0
  TxHeader1.MessageMarker = 0;	//marker++;	//Tx Event FIFO Use
  // 将数据添加到Tx Event FIFO队列中
  if(HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader1, tx_data) != HAL_OK) {
    // 如果添加失败,则设置发送失败标志
    fdcan1_send_fail.flag = 1;
    // 将TxHeader1的值复制到fdcan1_send_fail.TxHeader中
    memcpy(&fdcan1_send_fail.TxHeader, &TxHeader1, sizeof(FDCAN_TxHeaderTypeDef));
    // 将tx_data的值复制到fdcan1_send_fail.TxData中
    memcpy(fdcan1_send_fail.TxData, tx_data, can_dlc2len(DataLength));
		// 返回0
		return 0;
  } 
	// 返回1
	return 1;
}

  uint32_t count = 0;
  uint32_t cnt_100us = 0;
  uint32_t cnt_500us = 0;
  uint8_t tim4_flag = 0;
void canfd_test(void)
{
	  for(uint8_t i = 0; i < 64; i++) {
        TxData1[i] = i;
    }
  if(count < 1000) {
      TxData1[0] = count >> 8 & 0xFF;
      TxData1[1] = count & 0xFF;

      ++cnt_100us;
      cnt_500us = cnt_100us / 5;
      if(cnt_500us && (cnt_100us%5==0) ) {
        switch(cnt_500us) {
          case 1: fdcan1_transmit(0x123, FDCAN_DLC_BYTES_64, TxData1); 
                  fdcan1_transmit(0x124, FDCAN_DLC_BYTES_64, TxData1);
                  fdcan1_transmit(0x125, FDCAN_DLC_BYTES_64, TxData1); break;
          case 4: fdcan1_transmit(0x12345678, FDCAN_DLC_BYTES_64, TxData1); break;
          case 5: fdcan1_transmit(0x12345679, FDCAN_DLC_BYTES_64, TxData1); break;
          case 6: fdcan1_transmit(0x1234567A, FDCAN_DLC_BYTES_64, TxData1); break;
          case 7: /* next send */ break;
          case 8: break;
          case 20: ++count; cnt_100us = 0; break; //10ms
        }
      } else {  //fail retransmission once

        if(fdcan1_send_fail.flag) {
          fdcan1_transmit(fdcan1_send_fail.TxHeader.Identifier, 
                          fdcan1_send_fail.TxHeader.DataLength,
                          fdcan1_send_fail.TxData);
          fdcan1_send_fail.flag = 0;
        }
      }
    }

}

初始化

HAL_TIM_Base_Start_IT(&htim4);
fdcan1_config();
CANopen_App_Init(&slave_Data);

修改timerscfg.h

// The timer is incrementing every 10 us.
#define MS_TO_TIMEVAL(ms) ((ms) * 100U)
#define US_TO_TIMEVAL(us) ((us)/10)

根据实际修改,由于TIM4的时基100KHz(160Mhz/1600),也就是10us
经测试NMT的Boot_up (节点上线报文)、NMT 心跳报文 、NMT节点状态切换、SDO快速读、SDO快速写、SDO错误、PDO、节点守护均正常。

参考

【1】CanOpen协议【CanFestival】移植方法 支持VC、QT、STM32:https://bbs.21ic.com/icview-878522-1-1.html
【2】CANopen协议【CANFestival】移植方法:
https://www.cnblogs.com/ChYQ/p/5719469.html#:~:text=%E6%8E%A5%E4%B8%8B%E6%9D%A5%E5%BC%80%E5%A7%8B%E7%A7%BB%E6%A4%8D%EF%BC%9A%20%E6%AD%A5%E9%AA%A4%E4%B8%80%EF%BC%9A,%E5%9C%A8%E6%96%B0%E5%BB%BA%E5%A5%BD%E7%9A%84%E5%B7%A5%E7%A8%8B%E7%9B%AE%E5%BD%95%E4%B8%8B%E6%96%B0%E5%BB%BA%E6%96%87%E4%BB%B6%E5%A4%B9CanFestival%EF%BC%8C%E5%86%8D%E5%9C%A8CanFestival%E4%B8%8B%E6%96%B0%E5%BB%BA%E6%96%87%E4%BB%B6%E5%A4%B9driver%E3%80%81inc%E5%92%8Csrc%EF%BC%8C%E5%86%8D%E5%9C%A8inc%E6%96%87%E4%BB%B6%E5%A4%B9%E4%B8%8B%E9%9D%A2%E6%96%B0%E5%BB%BA%20stm32%E6%96%87%E4%BB%B6%E5%A4%B9%EF%BC%88%E6%88%91%E8%BF%99%E9%87%8C%E4%B8%BB%E8%A6%81%E4%BB%A5%E7%A7%BB%E6%A4%8D%E5%88%B0stm32%E4%B8%BA%E4%BE%8B%E8%AF%B4%E6%98%8E%EF%BC%8C%E5%A6%82%E6%9E%9C%E6%98%AF%E7%A7%BB%E6%A4%8D%E5%88%B0VC%E6%88%96%E5%85%B6%E4%BB%96%E5%B9%B3%E5%8F%B0%E4%B8%8B%EF%BC%8C%E8%BF%99%E9%87%8C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E5%91%BD%E5%90%8D%E4%B8%BA%E5%85%B6%E4%BB%96%E5%90%8D%E5%AD%97%EF%BC%8C%E5%A6%82vc%EF%BC%89%E3%80%82
【3】STM32无系统移植CanFestival小白教程:https://blog.csdn.net/weixin_43072093/article/details/115245443
【4】CANOPEN 学习(一) CANFestival 字典工具 环境搭建:https://blog.csdn.net/mobei1983/article/details/110879850?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-110879850-blog-115245443.pc_relevant_multi_platform_whitelistv1_exp2&spm=1001.2101.3001.4242.1&utm_relevant_index=3
【5】CanFestival字典生成:https://blog.csdn.net/lushoumin/article/details/92841982
【6】基于STM32F4的CANOpen移植教程(超级详细)_月落三千雪的博客-程序员秘密_canopen stm32:https://cxymm.net/article/qq_37662088/123261908
【7】驱动器使用 —— DS402状态切换(个人笔记):https://blog.csdn.net/weixin_43455581/article/details/103661372
【8】基于STM32F4的CANOpen移植教程(超级详细):
https://blog.csdn.net/qq_37662088/article/details/123261908
【9】CanFestival移植到STM32 F4芯片(基于HAL库):
https://www.iotword.com/32673.html
【10】B站CANopen视频
https://www.bilibili.com/video/BV1LF411E7KJ/?spm_id_from=333.1387.collection.video_card.click&vd_source=5f570a9f261c43941608688d2d31a4c5
【11】CANopen FD:
https://www.can-cia.org/can-knowledge/canopen-fd-the-art-of-embedded-networking
【12】STM32 CAN使用记录:FDCAN基础通讯:https://blog.csdn.net/Naisu_kun/article/details/132830048
【13】STM32G474_FDCAN的普通CAN模式使用(寄存器开发):https://shequ.stmicroelectronics.cn/thread-637235-1-1.html