)
CANopen移植到STM32F4平台前言1 物品准备2 相关软件安装2.1 CAN上位机2.2 对象字典生成工具objdictedit环境配置3 将CANopen移植到STM32F4073.1 基础代码移植3.11 h文件移植3.12 c文件移植3.2 建立自己的底层驱动文件3.3 建立词典3.4工程配置3.41 c文件添加3.42 头文件路径添加3.43 c99标准选择3.44 调试串口设置3.45 程序启动4 末尾本专题相关教程基于STM32F4的CANOpen移植教程基于STM32F4的CANopen快速SDO通信linux下CANopen for python的使用基于Linux C的CANopen移植CANopen补充–时间计算出错CANopen补充–主站检测节点是否在线前言为了在STM32F4上能够运行CANopenCanFestival跟着网上的教程操作发现总是不够详细。主要是配置和代码运行部分基本没有解释。为了后来者能够少走弯路便有了这篇教程。关于CANopen协议本身本文不做过多介绍主要是介绍如何使用软件和代码修改。本文配套资料下载地址https://pan.baidu.com/s/1FMp7xuJ1r3gJTPB0wf3Yrw?pwdosxs提取码osxs废话不多说GOGOGO。1 物品准备名称用途USB-CAN模块/USB-CAN盒子用以监听数据如实在没有的话代码里添加串口反馈也勉强能测试Canfestival- 3 源代码CANopen源代码/本文资料里有STM32F4 裸工程移植目标平台代码,这里用正点原子的空白工程即可/本文资料里有CANopen轻松入门.pdf-周立功pdf书籍用以学习CANopen协议/本文资料里有USB-CAN模块比如下面这个买啥都行有这个功能就ok。USB-CAN盒子如下相比模块多了一些功能我用的就这个不过好像多的功能我并没有用上CANopen轻松入门.pdf-周立功链接 下载地址2 相关软件安装2.1 CAN上位机如果使用USB-CAN盒子找店家要上位机资料即可。比如我用的这款资料如下驱动驱动下载驱动安装教程驱动安装视频上位机软件上位机下载地址打开设备-选择设备-选择对应波特率即可。如果是普通的USB-CAN模块找店家应该也有资料。使用CANpro协议平台分析软件即可这个网上搜很容易搜得到。附一个我随便找的链接CANPro协议分析平台官方下载同理启动-选择设备不对就反复选-选择波特率。2.2 对象字典生成工具objdictedit环境配置 CANopen需要使用到字典路径源代码/objdictgen/objdictedit.py。这是个基于python2.7才能运行的程序因此我们先装环境。安装环境遇到了很多坑。主要是网上教程很多偏老跟着操作各种bug。最终成功的一个搭配是软件名字备注python-2.7.15.amd64.msiwxPython3.0-win64-3.0.2.0-py27.exe使用2.8会导致在安装下边软件的时候提示包缺失Gnosis_Utils-1.2.2.zip安装教程参考CanFestival中对象字典编辑器objdictedit的正确打开环境_lei_g的博客-CSDN博客_canfestival中对象字典编辑器的打开备注python2.7和自己之前安装的如python3.7是不冲突的。要使用objdictedit可以使用这个方式固定到任务栏。方便以后打开。选择默认程序–更多应用–在这台电脑上查找其他应用–选择python2.7文件夹里的python.exe当打开下边程序的时候在桌面任务栏选择固定到任务栏。那么以后都可以右键这个图标点击上边的objdictedit.py即可打开软件。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8awyPdNN-1646308038808)(C:\Users\FEN\AppData\Roaming\Typora\typora-user-images\image-20220303104232531.png)]3 将CANopen移植到STM32F407首先这是我给大家准备的礼物截图如下名称说明CanFestival-3源码内含1个官方源码与3个分支我们使用Mongo-canfestival因为它有对于cm4内核的支持CANopen裸工程本教程所使用的空白代码CANopen最终移植代码本教程所使用的移植好的最终代码USBCAN调试软件USB-CAN盒子的上位机字典工具安装字典工具安装所需文件3.1 基础代码移植打开我们的空白工程界面如图空空如也。需要说明的是个人喜欢把所有头文件放入main.h这样其他外设文件只用包含main.h即可。文件夹如下我们新建一个文件夹名为CANopen用于存放所有与CANopen有关的代码。里面再新建几个子文件夹。说明如下文件夹名说明dictionary存放字典和其对应的.c /.h 文件hardware外设的驱动文件如定时器CAN还有配置文件inc由CANopen源代码移植过来的h文件src由CANopen源代码移植过来的c文件3.11 h文件移植进入源代码/include目录先将该目录下19个h文件都复制到新工程/CANopen/inc 里再复制cm4文件夹内含3个h文件更名为stm32。如下修改一下stm32/canfestival.h文件,添加3行语句防止递归调用。进入源代码\examples\AVR\Slave目录把config文件移植到新工程/CANopen/hardware并对config做一点修改。3.12 c文件移植进入源代码/src目录将该目录下除了symbols.c之外的12个c文件复制到新工程/CANopen/src 里。删除dcf.c文件下第59、98行前面的“inline”关键字3.2 建立自己的底层驱动文件 在裸工程/CANopen/hardware下新建定时器、CAN的c/h文件。其中定时器用于时间获取CAN是通信基础。需要说明的是CANopen源代码里含有timer.c 文件为了命名不冲突我这里起名加了后缀。比如使用定时器3就建立timer3.c。如图我们使用了can1timer2 config.h为之前移植的文件不用管。文件说明can1中断优先级为1无所谓波特率设置为1M1M或者500K都行要与config.h一致timer2中断优先级1无所谓时钟84M分频840,即基础频率为100K要求与timerscfg.h里的配置即可重装载值为65535即0.65s一次溢出中断can与timer的代码移植自源代码/drivers/cm4。cm4是基于stm32F3的因此有些代码需要修改cm4.c里面包含can1与timer3的初始化代码以及一些封装好的代码。我们将其各自复制到can1.c和timer3.c。并根据板子情况做修改。大家可以到移植成功的工程里看看有啥修改。can1.c#includecan1.hstaticCO_Data*co_dataNULL;//Initialize the CAN hardwareunsignedcharCAN1_Init(CO_Data*d,uint32_tbitrate){GPIO_InitTypeDef GPIO_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;CAN_InitTypeDef CAN_InitStructure;CAN_FilterInitTypeDef CAN_FilterInitStructure;/* save the canfestival handle */co_datad;/* CAN GPIOs configuration **************************************************//* Enable GPIO clock */RCC_AHB1PeriphClockCmd(CAN_GPIO_CLK,ENABLE);/* Connect CAN pins to AF7 */GPIO_PinAFConfig(CAN_GPIO_PORT,CAN_RX_SOURCE,GPIO_AF_CANx);GPIO_PinAFConfig(CAN_GPIO_PORT,CAN_TX_SOURCE,GPIO_AF_CANx);/* Configure CAN RX and TX pins */GPIO_InitStructure.GPIO_PinCAN_RX_PIN|CAN_TX_PIN;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AF;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_InitStructure.GPIO_OTypeGPIO_OType_PP;GPIO_InitStructure.GPIO_PuPdGPIO_PuPd_UP;GPIO_Init(CAN_GPIO_PORT,GPIO_InitStructure);/* NVIC configuration *******************************************************/NVIC_InitStructure.NVIC_IRQChannelCANx_RX0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority0x0;NVIC_InitStructure.NVIC_IRQChannelSubPriority0x0;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);/* CAN configuration ********************************************************//* Enable CAN clock */RCC_APB1PeriphClockCmd(CAN_CLK,ENABLE);/* CAN register init */CAN_DeInit(CANx);CAN_StructInit(CAN_InitStructure);/* CAN cell init */CAN_InitStructure.CAN_TTCMDISABLE;CAN_InitStructure.CAN_ABOMDISABLE;CAN_InitStructure.CAN_AWUMDISABLE;CAN_InitStructure.CAN_NARTDISABLE;CAN_InitStructure.CAN_RFLMDISABLE;CAN_InitStructure.CAN_TXFPDISABLE;CAN_InitStructure.CAN_ModeCAN_Mode_Normal;CAN_InitStructure.CAN_SJWCAN_SJW_1tq;/* CAN Baudrate (CAN clocked at 42 MHz) 42e6 / ( 6 * (1BS1BS2)) */CAN_InitStructure.CAN_SJWCAN_SJW_1tq;if(bitrate1000000){CAN_InitStructure.CAN_BS1CAN_BS1_3tq;CAN_InitStructure.CAN_BS2CAN_BS2_3tq;}else{//除去1M频率。剩下都配置为500KCAN_InitStructure.CAN_BS1CAN_BS1_6tq;CAN_InitStructure.CAN_BS2CAN_BS2_7tq;}CAN_InitStructure.CAN_Prescaler6;CAN_Init(CANx,CAN_InitStructure);/* CAN filter init */CAN_FilterInitStructure.CAN_FilterNumber0;CAN_FilterInitStructure.CAN_FilterModeCAN_FilterMode_IdMask;CAN_FilterInitStructure.CAN_FilterScaleCAN_FilterScale_32bit;CAN_FilterInitStructure.CAN_FilterIdHigh0x0000;CAN_FilterInitStructure.CAN_FilterIdLow0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdHigh0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdLow0x0000;CAN_FilterInitStructure.CAN_FilterFIFOAssignment0;CAN_FilterInitStructure.CAN_FilterActivationENABLE;CAN_FilterInit(CAN_FilterInitStructure);/* Enable FIFO 0 message pending Interrupt */CAN_ITConfig(CANx,CAN_IT_FMP0,ENABLE);return1;}// The driver send a CAN message passed from the CANopen stackunsignedcharcanSend(CAN_PORT notused,Message*m){inti,res;CanTxMsg TxMessage{0};TxMessage.StdIdm-cob_id;TxMessage.IDECAN_ID_STD;if(m-rtr)TxMessage.RTRCAN_RTR_REMOTE;elseTxMessage.RTRCAN_RTR_DATA;TxMessage.DLCm-len;for(i0;im-len;i)TxMessage.Data[i]m-data[i];resCAN_Transmit(CANx,TxMessage);if(resCAN_TxStatus_NoMailBox)return0;// errorreturn1;// succesful}//The driver pass a received CAN message to the stack/* unsigned char canReceive(Message *m) { } */unsignedcharcanChangeBaudRate_driver(CAN_HANDLE fd,char*baud){return0;}/** * brief This function handles CAN1 RX0 interrupt request. * param None * retval None */voidCAN1_RX0_IRQHandler(void){inti;CanRxMsg RxMessage{0};Message rxm{0};CAN_Receive(CAN1,CAN_FIFO0,RxMessage);// Drop extended framesif(RxMessage.IDECAN_ID_EXT)//不处理扩展帧return;rxm.cob_idRxMessage.StdId;if(RxMessage.RTRCAN_RTR_REMOTE)//远程帧rxm.rtr1;rxm.lenRxMessage.DLC;for(i0;irxm.len;i)rxm.data[i]RxMessage.Data[i];canDispatch(co_data,rxm);//CANopen自身的处理函数因为快速SDO不需要反馈所以在上边处理后就不需要调用这步了}can1.h#ifndef__CAN1_H#define__CAN1_H#includesys.h#includemain.h#includedata.h// CAN bus defines for cortex-M4 STM32F407#defineCANxCAN1#defineCAN_CLKRCC_APB1Periph_CAN1#defineCAN_RX_PINGPIO_Pin_11#defineCAN_TX_PINGPIO_Pin_12#defineCAN_GPIO_PORTGPIOA#defineCAN_GPIO_CLKRCC_AHB1Periph_GPIOA#defineCANx_RX0_IRQnCAN1_RX0_IRQn#defineGPIO_AF_CANxGPIO_AF_CAN1#defineCAN_RX_SOURCEGPIO_PinSource11#defineCAN_TX_SOURCEGPIO_PinSource12unsignedcharCAN1_Init(CO_Data*d,uint32_tbitrate);#endiftimer3.c#includetimer3.hTIMEVAL last_counter_val0;TIMEVAL elapsed_time0;// Initializes the timer, turn on the interrupt and put the interrupt time to zerovoidTIM3_Init(void){NVIC_InitTypeDef NVIC_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;/* TIM3 clock enable */RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);/* Enable the TIM3 gloabal Interrupt */NVIC_InitStructure.NVIC_IRQChannelTIM3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority0;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);/* Compute the prescaler value */uint16_tPrescalerValue840-1;//84M频率/840为100k(与timerscfg.h配置一致即可)即10us间隔/* Time base configuration */TIM_TimeBaseStructure.TIM_Period65535;TIM_TimeBaseStructure.TIM_PrescalerPrescalerValue;TIM_TimeBaseStructure.TIM_ClockDivision0;TIM_TimeBaseStructure.TIM_CounterModeTIM_CounterMode_Up;TIM_TimeBaseInit(TIM3,TIM_TimeBaseStructure);TIM_ClearITPendingBit(TIM3,TIM_SR_UIF);/* TIM3 enable counter *///这里需要启动定时器TIM_Cmd(TIM3,ENABLE);/* Preset counter for a safe start */TIM_SetCounter(TIM3,1);/* TIM Interrupts enable */TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);}//Set the timer for the next alarm.voidsetTimer(TIMEVAL value){uint32_ttimerTIM_GetCounter(TIM3);// Copy the value of the running timerelapsed_timetimer-last_counter_val;last_counter_val65535-value;TIM_SetCounter(TIM3,65535-value);TIM_Cmd(TIM3,ENABLE);//printf(setTimer %lu, elapsed %lu\r\n, value, elapsed_time);}//Return the elapsed time to tell the Stack how much time is spent since last call.TIMEVALgetElapsedTime(void){uint32_ttimerTIM_GetCounter(TIM3);// Copy the value of the running timerif(timerlast_counter_val)timer65535;TIMEVAL elapsedtimer-last_counter_valelapsed_time;//printf(elapsed %lu - %lu %lu %lu\r\n, elapsed, timer, last_counter_val, elapsed_time);returnelapsed;}// This function handles Timer 3 interrupt request.voidTIM3_IRQHandler(void){//printf(--\r\n);if(TIM_GetFlagStatus(TIM3,TIM_SR_UIF)RESET)return;last_counter_val0;elapsed_time0;TIM_ClearITPendingBit(TIM3,TIM_SR_UIF);TimeDispatch();}timer3.h#ifndef__TIMER3_H#define__TIMER3_H#includesys.h#includemain.hvoidTIM3_Init(void);#endif2022年3月18日记定时器实现函数存在缺陷当超过一个功能需要调用时间时会存在干涉。各位如果除了心跳报文发送之外没用到其他需要时间的功能节点掉线检测/pdo之类那么可以忽略。不然可以看一下这个CANopen补充–时间计算出错。3.3 建立词典我们起名字为Master使用心跳管理这样我们待会便可以通过心跳报文来判断移植成功与否。在字典里设置心跳报文间隔为1000ms0x3E8。这样它每隔1000ms就会发送一个心跳报文。点击保存将生成的.od文件放入CANopen/dictionnary文件夹。再点击建立词典同样将生成的.c文件放入CANopen/dictionnary文件夹。效果如下文件说明.od文件词典工程文件用于配置不会被工程调用.c .h词典文件对应的c和h文件。需要被工程调用3.4工程配置文件都弄好了我们打开keil软件将这些文件都加入到工程。3.41 c文件添加在Groups里新建两个文件夹。需要说明的时候为了美观这里把词典文件和外设驱动文件放在一起了。文件夹说明CANopen含CANopen/srcCANopen_Driver含CANopen/hardware 和CANopen/dictionary。3.42 头文件路径添加3.43 c99标准选择由于源码很多地方把定义语句放在赋值语句之后这只在C99标准之后允许因此勾选C99模式。3.44 调试串口设置 使用工程自带的USART1。警告在项目中正常运行后一定要关闭调试功能不然串口发送数据会严重降低相应速度我们打开applicfg.h 如果找不到直接全局搜索MSG(…) 便可定位到啦。第一添加debug的定义再次警告在项目中正常运行后记得关闭把定义注释掉第二把打印函数里的\n 改成\r\n。如图是串口反馈的效果还是挺直观的。没有USB-CAN的同学可以通过串口调试助手来观察。3.45 程序启动首次在main.h里添加相关头文件main函数添加canopen初始化。包含定时器3、串口1、can1的初始化#includesys.h#includemain.hintmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4delay_init(168);//初始化延时函数TIM3_Init();USART1_Init(115200);CAN1_Init(Master_Data,1000000);unsignedcharnodeID0x00;//主站IDsetNodeId(Master_Data,nodeID);setState(Master_Data,Initialisation);//节点初始化setState(Master_Data,Operational);while(1){delay_ms(1000);}}下载启动使用软件观察。心跳没有问题nice如果大家有需要让主站检测节点是否掉线的需要可以看CANopen补充–主站检测节点是否在线。4 末尾 到这里便移植成功啦。下一篇教程基于STM32F4的CANopen快速SDO通信超级详细