CanFestival移植到STM32 F4芯片(基于HAL库)

本文讲述如何通过简单操作就可以把CanFestival库移植到STM32 F4芯片上,作为Slave设备。使用启明欣欣的工控板来做实验。


一 硬件连接

观察CAN报文需要专门的设备,本人从某宝上买了一个兼容PCAN的开源小板子,二十几块钱,通过USB接到电脑后就可以使用PCAN-View打开进行监控,非常方便,同时还带开关控制的120欧姆终端电阻,缺点是有时不太稳定,

![[Pasted image 20240831172709.png]]

这个小板子还需要通过CAN线和启明欣欣板子上的CAN口进行连接,如下图,这里连接的是CAN2,

![[Pasted image 20240831173148.png]]

工控板需要单独供电,不能依靠下载器的电,否则无法正常运行。最后打开小板子上的120欧姆电阻,开关拨到ON那一端就可以了。

PS:PCAN-View可以去PeakCAN官网下载,是免费软件;CAN线得是双绞线,H对H,L对L进行连接。


二 搭建CAN基础工程

硬件搭建好之后,先搭建一个CAN的基础工程,验证是否可以收发CAN消息,为后续移植打下基础。这里使用STM32CubeMX来操作。

打开STM32CubeMX,芯片选择STM32F407ZGT6,选好后创建工程。

配置时钟

在新界面里的Pinout & Configuration里选择RCC,然后高速时钟和低速时钟都选择Crystal/Ceramic Resonator

![[Pasted image 20240831181308.png]]

PS:启明欣欣的板子,其外部接了2个晶振,一个8M,一个32.768K,前者用于高速时钟,后者用于低速时钟,如果低速时钟没有外接晶振,那么就选disable。

此时点击Clock Configuration

![[Pasted image 20240831181611.png]]

然后按照红框里的步骤一个个更改,

![[Pasted image 20240831181644.png]]

第4步输入频率168后回车,CubeMX会自动调整,非常方便。另外,可以看到低速时钟LSE的输入频率是32.768K,这个也可以修改,需要结合实际。

这里要注意一下:配置完毕后APB1外设时钟是42M,APB1定时器时钟是84M,后面要用到。

设置SYS

回到Pinout & Configuration,然后点击SYS,右侧的选项按照如下进行选择,

![[Pasted image 20240831181843.png]]

PS:由于没有使用操作系统,所以这里的Timebase Source选择SysTick

开启CAN2

启明欣欣的CAN2对应的是PB12和PB13,其中PB12是RX,PB13是TX,先在Pinout view里右下角的搜索框中输入PB12,然后回车,就可以找到PB12,会闪烁,

![[Pasted image 20240831182433.png]]

点击该引脚,功能选择CAN2_RX,

![[Pasted image 20240831182632.png]]

同理找到PB13,将其设置为CAN2_TX,设置OK后会发现CAN2已经自动使能了。

![[Pasted image 20240831185148.png]]

这里设置一下CAN的通信速率,本教程使用500K,设置如上图红框,

  • Prescaler设置为6
  • Time Quanta in Bit Segment 1设置为7
  • Time Quanta in Bit Segment 2设置为6
  • ReSynchronization Jump Width设置为1
  • 经过上述设置后,CAN的波特率就变成500K了。

    设置原理:前面配置时钟时,APB1的外设时钟是42M,而CAN是属于APB1的外设,这样经过Prescaler的变频后就变成42M/6=7M,也就是7000K,然后除以(Time Quanta in Bit Segment 1 + Time Quanta in Bit Segment 2 + ReSynchronization Jump Width * 1),也就是7000K/14=500K

    芯片手册上的波特率的计算原理如下,

    ![[Pasted image 20240831185438.png]]

    可以看出上述配置并不是唯一选择,只要根据原理让波特率是500K就可以了。

    最后是开启CAN2的接收中断,如下红框,勾选CAN2 RX0中断,

    ![[Pasted image 20240831185918.png]]

    PS:RX0和RX1分别对应2个内部接收FIFO,这里选择FIFO0,后面会讲到如何设置

    生成Keil工程

    配置完毕,最后生成工程。

    点击Project Manager,然后在Project里对红框标注的地方进行自定义修改

    ![[Pasted image 20240818090956.png]]

    接着是Code Generator,按照如下勾选,也可以根据自己需要进行修改

    ![[Pasted image 20240818091151.png]]

    设置完毕后点击右上角的GENERATE CODE来生成Keil工程

    PS:Keil现在有社区版,个人使用是免费的,不用去破解了。

    验证CAN通信

    打开生成的Keil工程,然后先做以下配置,

  • 编译器使用V6版本

    ![[Pasted image 20240831190813.png]]

    如果使用的是老版的Keil,可能还得用V5版本的编译器

  • 取消勾选Browse Information,节约编译时间

    ![[Pasted image 20240831190907.png]]

  • 编译优化选项选择O1或O0,优化选项过高可能会造成调试时无法打断点

    ![[Pasted image 20240831190940.png]]

  • 配置好之后,编译一下,保证工程可以顺利编译完成。

    剩下是修改代码,主要参考这篇文章,不过该作者用的CAN1,这里再写一遍,在USER CODE BEGIN 4下面添加函数CANFilter_Config(),用于配置CAN2的过滤器,

    void CANFilter_Config(void)
    {
      CAN_FilterTypeDef  sFilterConfig;
      
      sFilterConfig.FilterBank = 0;                       // CAN过滤器编号,范围0-27
      sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;   // CAN过滤器模式,掩码模式或列表模式,这里选择掩码模式
      sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;  // CAN过滤器尺度,16位或32位,这里选择32位
      sFilterConfig.FilterIdHigh = 0x0000;                // 32位下,存储要过滤ID的高16位
      sFilterConfig.FilterIdLow  = 0x0000;                // 32位下,存储要过滤ID的低16位
      sFilterConfig.FilterMaskIdHigh = 0x0000;            // 掩码模式下,存储的是过滤器掩码的高16位
      sFilterConfig.FilterMaskIdLow  = 0x0000;            // 掩码模式下,存储的是过滤器掩码的低16位
      sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 报文通过过滤器的匹配后,存储到哪个FIFO,这里选择FIFO0
      sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; // 激活过滤器
      sFilterConfig.SlaveStartFilterBank = 0;
      
      if (HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig) != HAL_OK) 
      {
        Error_Handler();
      }
    }
    

    选择FilterBank 0,配置成掩码模式,选择FIFO0来接收CAN报文(这个和前面选择RX0中断保持一致,如果使用FIFO1,那么前面就要勾选RX1中断)。掩码ID是全0,表示允许接收所有报文,芯片手册介绍如下,

    ![[Pasted image 20240831224249.png]]

    PS:如果只想接收指定报文,那么就要配置掩码ID为非0,具体可以参考芯片手册。

    然后添加CAN2的使能函数,

    void CAN_Start_Init(void)
    {
      if (HAL_CAN_Start(&hcan2) != HAL_OK) 
      {
        Error_Handler();
      }
    
      if (HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING) !=  HAL_OK)
      {
        Error_Handler();
      }
    }
    

    还有发送函数,这个用来测试发送,

    void CAN2_Send_Test(void)
    {
      uint32_t TxMailbox;
      uint8_t data[4] = {0x01, 0x02, 0x03, 0x04};
    
      CAN_TxHeaderTypeDef TxMessage;
      TxMessage.IDE = CAN_ID_STD;     // 设置ID类型,标准帧还是扩展帧,这里选择标准帧
      TxMessage.StdId = 0x111;        // 设置ID号
      TxMessage.RTR = CAN_RTR_DATA;   // 设置传输数据帧
      TxMessage.DLC = 4;              // 设置数据长度
    
      if (HAL_CAN_AddTxMessage(&hcan2, &TxMessage, data, &TxMailbox) != HAL_OK)
      {
        Error_Handler();
      }
    }
    

    最后是CAN2接收中断的回调函数,用来测试接收,

    void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
    {
        uint8_t data[8] = {0, 0, 0, 0, 0, 0, 0, 0};
        HAL_StatusTypeDef  status;
        CAN_RxHeaderTypeDef RxMessage;
        
        if (hcan == &hcan2) 
        {  
          status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxMessage, data);
          if (HAL_OK == status)
          {
            ; // to do something
          }
        }
    }
    

    添加好之后,在USER CODE BEGIN PFP下添加函数声明,接收中断的回调函数不用声明,

    /* USER CODE BEGIN PFP */
    
    void CANFilter_Config(void);
    void CAN_Start_Init(void);
    void CAN2_Send_Test(void);
    
    /* USER CODE END PFP */
    

    然后在main函数里调用这些函数,每隔1s发送一次CAN报文,

    ![[Pasted image 20240831225340.png]]

    再次编译并烧录到板子里,然后重启运行,此时在PCAN-View上可以看到CAN报文,说明发送没问题,

    ![[Pasted image 20240831230812.png]]

    接着验证接收,此时先进入debug模式,然后在CAN接收中断里打个断点,

    ![[Pasted image 20240831231041.png]]

    使用PCAN-View来创建一条随意报文,数据是0x9988,

    ![[Pasted image 20240831231158.png]]

    然后点击OK进行保存,最后选中创建的报文按空格进行发送,此时会进入断点,

    ![[Pasted image 20240831231403.png]]

    把data数组添加到Keil的Watch窗口中,然后单步走一步,可以看到data数组里有数据了,如下,正是之前发送的报文数据,

    ![[Pasted image 20240831231533.png]]


    三 移植CanFestival

    有了第二节的基础工程后,本节讲述如何移植CanFestival,适用于STM32 F4系列芯片。

    对象字典的配置

    首先规划一下Slave设备的对象字典,具体如下,

  • 每隔3s发送一次心跳报文
  • 添加对象0x2000_00 (u8类型), 0x2001_00 (u16类型), 0x2002_00 (u32类型), 0x2003_00 (i32类型),0x2004_00 (i16类型),0x2005_00 (Visible String类型),总共6个对象
  • RPDO1:对应0x2000_00,0x2001_00和0x2002_00
  • TPDO1:对应0x2003_00,每隔500ms发一次
  • 这里使用objdictgen来进行编辑,其地址是https://github.com/happybruce/objdictgen,经过本人优化后可以使用Python3启动。
    PS:这个编辑器原本是在CanFestival里自带的,本人把其独立出来了。

    双击objdictedit.py打开编辑器,然后在File下点击New,在弹出的界面里进行以下设置,
    ![[Pasted image 20241201214413.png]]

    点击OK,进入新的主界面,然后根据下述步骤进行配置,

    配置心跳报文

    接着在0x1000-0x1029里找到0x1017,将其值设置为3000 (其单位是毫秒,16进制是0xBB8)
    ![[Pasted image 20241201214707.png]]

    添加对象

    在0x2000-0x5FFF里添加之前提到的6个对象,
    ![[Pasted image 20241202230355.png]]

    配置RPDO1

    设置RPDO1的映射,该对象索引是0x1600,如下,对应0x2000_00, 0x2001_00和0x2002_00
    ![[Pasted image 20241202225034.png]]

    设置RPDO1的通信参数,该对象索引是0x1400,这里只配置COB ID和Inhibit Time (10ms)
    ![[Pasted image 20241202225258.png]]

    当设备收到COB ID的为NodeId+0x200的报文,就会把报文里包含的值存入对应的对象里。

    配置TPDO1

    设置TPDO1的映射,该对象索引是0x1A00,如下,对应0x2003_00
    ![[Pasted image 20241202225614.png]]

    设置TPDO1的通信参数,该对象索引是0x1800,如下,
    ![[Pasted image 20241202225824.png]]

    COB ID是nodeid+0x180,传输类型是254,即0xFE,定时时间是500ms,即0x1F4

    这样对象字典就配置好了。最后保存工程,并生成对应的代码,点击File->Build Dictionary生成slavedic.c和slavedic.h
    ![[Pasted image 20241202230906.png]]

    这2个文件暂时留着备用

    开启定时器

    打开CubeMX工程,然后开启TIM3,时钟源选择内部时钟,预分频系数输入839,

    ![[Pasted image 20240901133442.png]]

    为什么是839呢?CanFestival对定时器的要求是counter每隔10us加1,也就是100KHz,而前面配置时钟时定时器的时钟频率是84MHz,如果以100KHz为单位,那么84M就等于840x100K, 那么为了达到100KHz的频率,预分频系数就是840-1=839

    最后打开定时器中断,
    ![[Pasted image 20240901134020.png]]

    移植

    首先使用git clone下载CanFestival,

    git clone https://github.com/happybruce/CanFestival.git
    cd CanFestival
    git checkout develop
    

    这个Github地址是本人的仓库,已经对代码进行了优化。

    在工程目录Core下添加以下目录结构,

    ![[Pasted image 20240901125648.png]]

    然后做以下拷贝,

  • 把CanFestival/src目录下的红框标注的文件拷贝到Core/CanFestival/src/下,红叉的不要拷贝

  • 把CanFestival/include目录下的以下文件拷贝到Core/CanFestival/inc/下,还有cm4目录也拷贝过来,

  • 把CanFestival/drivers/cm4目录下的cm4.h和cm4.c拷贝到Core/CanFestival/drv下

  • 把CanFestival/examples/CM4/od/下的slavedic.c和slavedic.h拷贝到Core/CanFestival/slavedic下

  • 拷贝完毕后打开Keil工程,点击以下按钮,

    然后添加三个组及其对应的源文件,注意这里不需要添加头文件,后面给头文件设置搜索目录即可

    添加完毕后点击OK,然后添加搜索目录,如下图2个步骤,

    在弹出界面里填入以下目录,

    最后点击OK,这样工程就配置好了。

    main.c内容如下,

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2024 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "can.h"
    #include "tim.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "canfestival.h"
    #include "slavedic.h"
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    void CANFilter_Config(void);
    void CAN_Start_Init(void);
    void CAN1_Send_Test(void);
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
    
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_CAN2_Init();
      MX_TIM3_Init();
      /* USER CODE BEGIN 2 */
    
      initTimer(); // 初始化定时器
      canInit(&slavedic_ObjDictData, 500000); // 设置速率为500K
    
      HAL_TIM_Base_Start_IT(&htim3);
    
      CANFilter_Config();
      CAN_Start_Init();
    
      setNodeId(&slavedic_ObjDictData, 1); // 设置Canopen id为1
      setState(&slavedic_ObjDictData, Initialisation); // NMT状态设置为Initialisation
      setState(&slavedic_ObjDictData, Pre_operational); // NMT状态设置为Pre_operational
      setState(&slavedic_ObjDictData, Operational); // NMT状态设置为Operational
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        HAL_Delay(1000);
    
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Configure the main internal regulator output voltage
      */
      __HAL_RCC_PWR_CLK_ENABLE();
      __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLM = 4;
      RCC_OscInitStruct.PLL.PLLN = 168;
      RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
      RCC_OscInitStruct.PLL.PLLQ = 4;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    // static CAN_TxHeaderTypeDef TxMessage; //CAN发�?�的消息的消息头
    // static CAN_RxHeaderTypeDef RxMessage; //CAN接收的消息的消息�???
    
    void CANFilter_Config(void)
    {
        CAN_FilterTypeDef  sFilterConfig;
        
        sFilterConfig.FilterBank = 0;                       //CAN过滤器编号,范围0-27
        sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;   //CAN过滤器模式,掩码模式或列表模�???
        sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;  //CAN过滤器尺度,16位或32�???
        sFilterConfig.FilterIdHigh = 0x000 << 5;      //32位下,存储要过滤ID的高16�???
        sFilterConfig.FilterIdLow = 0x0000;          //32位下,存储要过滤ID的低16�???
        sFilterConfig.FilterMaskIdHigh = 0x0000;      //掩码模式下,存储的是掩码
        sFilterConfig.FilterMaskIdLow = 0x0000;
        sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;        //报文通过过滤器的匹配后,存储到哪个FIFO
        sFilterConfig.FilterActivation = CAN_FILTER_ENABLE;        //�???活过滤器
        sFilterConfig.SlaveStartFilterBank = 0;
        
        if (HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig) != HAL_OK) 
        {
          Error_Handler();
        }
    
    }
    
    
    void CAN_Start_Init(void)
    {
      if (HAL_CAN_Start(&hcan2) != HAL_OK) 
      {
        Error_Handler();
      }
    
      
      if (HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO0_MSG_PENDING) !=  HAL_OK)
      {
        Error_Handler();
      }
    }
    
    
    void CAN1_Send_Test(void)
    {
      uint32_t TxMailbox;
      CAN_TxHeaderTypeDef TxMessage;
      uint8_t data[4] = {0x01, 0x02, 0x03, 0x04};
    
      TxMessage.IDE = CAN_ID_STD;     //设置ID类型
      TxMessage.StdId = 0x111;        //设置ID�???
      TxMessage.RTR = CAN_RTR_DATA;   //设置传�?�数据帧
      TxMessage.DLC = 4;              //设置数据长度
      if (HAL_CAN_AddTxMessage(&hcan2, &TxMessage, data, &TxMailbox) != HAL_OK)
      {
        Error_Handler();
      }  
    }
    
    
    
    
    // void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
    // {
    //   uint8_t  data[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    //   HAL_StatusTypeDef  status;
    //   CAN_RxHeaderTypeDef RxMessage;
      
    //   if (hcan == &hcan2)
    //   {  
    //     status = HAL_CAN_GetRxMessage(&hcan2, CAN_RX_FIFO0, &RxMessage, data);
    //     if (HAL_OK == status)
    //     {
    //         // printf("--->Data Receieve!\r\n");
    //         // printf("RxMessage.StdId is %#x\r\n",  RxMessage.StdId);
    //         // printf("data[0] is 0x%02x\r\n", data[0]);
    //         // printf("data[1] is 0x%02x\r\n", data[1]);
    //         // printf("data[2] is 0x%02x\r\n", data[2]);
    //         // printf("data[3] is 0x%02x\r\n", data[3]);
    //         // printf("<---\r\n");
    
    //         __nop();
            
    //     }
    //   }
    // }
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    

    然后编译,烧录到板子上运行,如下,可以看到TPDO1每隔500毫秒发一次

    由前面配置可知,TPDO1对应0x2003_00,这里通过PCAN view来发送SDO去修改这个对象值,

    发送完后,可以看出TPDO1发出的值就变了,变成我们通过SDO写的值

    然后通过PCAN View发一个RPDO1给板子,

    发完之后通过SDO去读取0x2000_00, 0x2001_00和0x2002_00的值,如下,可以看出与RPDO1发出的值相等


    四 总结

    本文讲述了如何把CanFestival移植到STM32 F4系列芯片上,由于本人优化了CanFestival库,所以移植起来比较简单。

    了解过程后,对于master或移植到CM3、CM0都很容易了。

    作者:爱就是恒久忍耐

    物联沃分享整理
    物联沃-IOTWORD物联网 » CanFestival移植到STM32 F4芯片(基于HAL库)

    发表回复