本文讲述如何通过简单操作把CanFestival移植到STM32F103C8T6上使用蓝远科技的STM32工控板执行下列操作获取本文源码工程gitclone https://github.com/happybruce/CanFestival.gitcdCanFestivalgitcheckout develop然后在examples\CM3\里即可找到名字为CAN_Test.zip一 硬件连接工控板需要单独供电不能依靠下载器的电否则无法正常运行。最后打开小板子上的120欧姆电阻开关拨到ON那一端就可以了。PSPCAN-View可以去PeakCAN官网下载是免费软件CAN线得是双绞线H对HL对L进行连接。二 搭建CAN基础工程硬件连接好之后我们使用STM32CubeMX来创建一个基础的CAN工程芯片选择STM32F103C8T6选好后创建工程。1. 配置时钟该工控板外部接了2个晶振一个8M一个32.768K前者用于高速时钟后者用于低速时钟如果低速时钟没有外接晶振那么就选disable。接着点击Clock Configuration主频HCLK设置为72MHz然后回车自动调整。注意配置完毕后APB1定时器时钟是72MHz后面要用到。2. 配置SYS按照红框选择即可因为没有操作系统所以选择SysTick3. 开启CANSTM32F103C8T6上只有一路CANCAN_TX对应PB9CAN_RX对应PB8右击这两个引脚然后选择对应功能引脚设置好之后会发现CAN功能已经自动开启了这里设置一下CAN的通信速率本教程使用500K设置如上图红框Prescaler设置为6Time Quanta in Bit Segment 1设置为7Time Quanta in Bit Segment 2设置为6ReSynchronization Jump Width设置为2经过上述设置后CAN的波特率就变成500K了。最后开启中断如下图红框这里把收发的中断都开了其实只开个RX0的接受中断也可以3. 生成Keil工程点击Project Manager然后在Project里对红框标注的地方进行自定义修改最低Heap大小设置为3K最低Stack大小设置为5K接着是Code Generator按照如下勾选也可以根据自己需要进行修改设置完毕后点击右上角的GENERATE CODE来生成Keil工程PSKeil现在有社区版个人使用是免费的不用去破解了。4. 简单配置一下Keil工程打开Keil工程点击魔术棒编译器选择V6版本取消生成浏览信息这个会造成编译异常缓慢降低优化等级便于调试配置完毕然后编译一下Keil工程三 验证通信可以参考移植STM32 F4的那篇文章这里不再重复验证直接移植四 移植本节讲述如何移植CanFestival适用于STM32 F4系列芯片由于CanFestival已经经过本人优化移植非常简单1. 对象字典的配置首先规划一下Slave设备的对象字典具体如下每隔3s发送一次心跳报文添加对象0x2000_00 (u32类型), 0x2001_00 (i16类型), 0x2002_00 (u8类型), 0x2003_00 (u8类型)0x2004_00 (u16类型)添加对象0x3000_00 (u32类型), 0x3001_00 (u16类型), 0x3002_00 (u8类型), 0x3003_00 (u8类型)0x3004_00 (u16类型)RPDO1映射0x2000_00和0x2004_00RPDO2映射0x2001_00RPDO3映射0x2002_00RPDO4映射0x2003_00TPDO1映射0x3000_00和0x3004_00每隔500ms发一次TPDO2映射0x3001_00每隔1000ms发一次TPDO3映射0x3002_00每隔1500ms发一次TPDO4映射0x3003_00每隔2000ms发一次这个规划和移植STM32 F4的那篇文章中是一样的。这里使用objdictgen来进行编辑其地址是https://github.com/happybruce/objdictgen经过本人优化后可以使用Python3启动。PS这个编辑器原本是在CanFestival里自带的本人把其独立出来了。添加过程可以参考移植STM32 F4的那篇文章不再赘述。对象字典就配置好后保存工程并生成对应的代码点击File-Build Dictionary生成slavedic.c和slavedic.h留着后面使用2. 开启定时器打开CubeMX工程然后开启TIM3时钟源选择内部时钟预分频系数输入719为什么是719呢CanFestival对定时器的要求是counter每隔10us加1也就是100KHz而前面配置时钟时定时器的时钟频率是72MHz如果以100KHz为单位那么72M就等于720x100K 那么为了达到100KHz的频率预分频系数就是720-17193. 移植CanFestival首先使用git clone下载CanFestivalgitclone https://github.com/happybruce/CanFestival.gitcdCanFestivalgitcheckout develop这个Github地址是本人的仓库已经对代码进行了优化。在工程目录Core下添加以下目录结构然后做以下拷贝把CanFestival/src目录下的红框标注的文件拷贝到Core/CanFestival/src/下把CanFestival/include目录下的以下文件拷贝到Core/CanFestival/include/下注意包含cm3目录把CanFestival/drivers/cm3目录下的cm3.c拷贝到Core/CanFestival/drv下把对象字典的配置这节生成的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) 2026 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 ------------------------------------------------------------------*/#includemain.h#includecan.h#includetim.h#includegpio.h/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes */#includecanfestival.h#includeslavedic.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 -----------------------------------------------*/voidSystemClock_Config(void);/* USER CODE BEGIN PFP */voidCANFilter_Config(void);voidCAN_Start_Init(void);/* USER CODE END PFP *//* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 *//* USER CODE END 0 *//** * brief The application entry point. * retval int */intmain(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_CAN_Init();MX_TIM3_Init();/* USER CODE BEGIN 2 */selectTimer(htim3);selectCAN(hcan);// baud rate is not needed here, because it is set in CubeMXcanInit(slavedic_ObjDictData,UINT32_MAX);HAL_TIM_Base_Start_IT(htim3);CANFilter_Config();CAN_Start_Init();setNodeId(slavedic_ObjDictData,1);// Set CANopen node id to be 1setState(slavedic_ObjDictData,Initialisation);// Set NMT state to InitialisationsetState(slavedic_ObjDictData,Pre_operational);// Set NMT state to Pre_operationalsetState(slavedic_ObjDictData,Operational);// Set NMT state to Operational/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while(1){HAL_Delay(1000);// 延时1秒/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */}/** * brief System Clock Configuration * retval None */voidSystemClock_Config(void){RCC_OscInitTypeDef RCC_OscInitStruct{0};RCC_ClkInitTypeDef RCC_ClkInitStruct{0};/** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */RCC_OscInitStruct.OscillatorTypeRCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEStateRCC_HSE_ON;RCC_OscInitStruct.HSEPredivValueRCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIStateRCC_HSI_ON;RCC_OscInitStruct.PLL.PLLStateRCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSourceRCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMULRCC_PLL_MUL9;if(HAL_RCC_OscConfig(RCC_OscInitStruct)!HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks */RCC_ClkInitStruct.ClockTypeRCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSourceRCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDividerRCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDividerRCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDividerRCC_HCLK_DIV1;if(HAL_RCC_ClockConfig(RCC_ClkInitStruct,FLASH_LATENCY_2)!HAL_OK){Error_Handler();}}/* USER CODE BEGIN 4 */voidCANFilter_Config(void){CAN_FilterTypeDef filterConfig;filterConfig.FilterBank0;// 过滤器组0filterConfig.FilterModeCAN_FILTERMODE_IDMASK;// 掩码模式filterConfig.FilterScaleCAN_FILTERSCALE_32BIT;// 32位尺度filterConfig.FilterIdHigh0x0000;// 高16位filterConfig.FilterIdLow0x0000;// 低16位filterConfig.FilterMaskIdHigh0x0000;// 掩码高16位filterConfig.FilterMaskIdLow0x0000;// 掩码低16位filterConfig.FilterFIFOAssignmentCAN_FILTER_FIFO0;// 分配到FIFO0filterConfig.FilterActivationCAN_FILTER_ENABLE;// 激活过滤器filterConfig.SlaveStartFilterBank0;if(HAL_CAN_ConfigFilter(hcan,filterConfig)!HAL_OK){Error_Handler();}}voidCAN_Start_Init(void){if(HAL_CAN_Start(hcan)!HAL_OK){Error_Handler();}if(HAL_CAN_ActivateNotification(hcan,CAN_IT_RX_FIFO0_MSG_PENDING)!HAL_OK){Error_Handler();}}voidHAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef*hcanPtr){if(hcanPtrhcan){handleCANPendingMessage();}}voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htimhtim3){handleTimerPeriodElapsed();}}/* USER CODE END 4 *//** * brief This function is executed in case of error occurrence. * retval None */voidError_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 */}#ifdefUSE_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 */voidassert_failed(uint8_t*file,uint32_tline){/* 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 */代码解析使用CANFilter_Config()来过滤接收报文这里选择是接收所有报文使用selectTimer()和selectCAN()来选择开启的外设老代码是在库里写死的这里优化了下由用户来选择更加灵活使用setNodeId()来设置自身的node id重写HAL_CAN_RxFifo0MsgPendingCallback()里面调用CanFestival提供的处理函数老代码是在库里写好的这里改成在外部由用户填写重写HAL_TIM_PeriodElapsedCallback()里面调用CanFestival提供的处理函数老代码是在库里写好的这里改成在外部由用户填写4. 验证同样可以参考本人写的移植STM32 F4芯片的那篇文章这里不再重复