CANopen移植到STM32F4平台

  • 前言
  • 1 物品准备
  • 2 相关软件安装
    • 2.1 CAN上位机
    • 2.2 对象字典生成工具objdictedit环境配置
  • 3 将CANopen移植到STM32F407
    • 3.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上能够运行CANopen(CanFestival),跟着网上的教程操作,发现总是不够详细。主要是配置和代码运行部分基本没有解释。为了后来者能够少走弯路,便有了这篇教程。关于CANopen协议本身本文不做过多介绍,主要是介绍如何使用软件和代码修改。

本文配套资料下载地址:https://pan.baidu.com/s/1FMp7xuJ1r3gJTPB0wf3Yrw?pwd=osxs
提取码: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.msi
wxPython3.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。

如图,我们使用了can1,timer2, config.h为之前移植的文件,不用管。

文件 说明
can1 中断优先级为1(无所谓);波特率设置为1M(1M或者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

#include "can1.h"static CO_Data *co_data = NULL;//Initialize the CAN hardware
unsigned char CAN1_Init(CO_Data * d, uint32_t bitrate)
{GPIO_InitTypeDef  GPIO_InitStructure;NVIC_InitTypeDef  NVIC_InitStructure;CAN_InitTypeDef        CAN_InitStructure;CAN_FilterInitTypeDef  CAN_FilterInitStructure;/* save the canfestival handle */  co_data = d;/* 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_Pin = CAN_RX_PIN | CAN_TX_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;GPIO_Init(CAN_GPIO_PORT, &GPIO_InitStructure);/* NVIC configuration *******************************************************/NVIC_InitStructure.NVIC_IRQChannel = CANx_RX0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;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_TTCM = DISABLE;CAN_InitStructure.CAN_ABOM = DISABLE;CAN_InitStructure.CAN_AWUM = DISABLE;CAN_InitStructure.CAN_NART = DISABLE;CAN_InitStructure.CAN_RFLM = DISABLE;CAN_InitStructure.CAN_TXFP = DISABLE;CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;/* CAN Baudrate (CAN clocked at 42 MHz)  42e6 / ( 6 * (1+BS1+BS2))  */CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;if(bitrate == 1000000){CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq;CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;}else {    //除去1M频率。剩下都配置为500KCAN_InitStructure.CAN_BS1 = CAN_BS1_6tq;CAN_InitStructure.CAN_BS2 = CAN_BS2_7tq;}CAN_InitStructure.CAN_Prescaler = 6;CAN_Init(CANx, &CAN_InitStructure);/* CAN filter init */CAN_FilterInitStructure.CAN_FilterNumber = 0;CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;CAN_FilterInit(&CAN_FilterInitStructure);/* Enable FIFO 0 message pending Interrupt */CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);return 1;
}// The driver send a CAN message passed from the CANopen stack
unsigned char canSend(CAN_PORT notused, Message *m)
{int i, res;CanTxMsg TxMessage = {0};TxMessage.StdId = m->cob_id;TxMessage.IDE = CAN_ID_STD;if(m->rtr)TxMessage.RTR = CAN_RTR_REMOTE;elseTxMessage.RTR = CAN_RTR_DATA;TxMessage.DLC = m->len;for(i=0 ; i<m->len ; i++)TxMessage.Data[i] = m->data[i]; res = CAN_Transmit(CANx, &TxMessage);if(res == CAN_TxStatus_NoMailBox)return 0;    // errorreturn 1;       // succesful
}//The driver pass a received CAN message to the stack
/*
unsigned char canReceive(Message *m)
{
}
*/
unsigned char canChangeBaudRate_driver( CAN_HANDLE fd, char* baud)
{return 0;
}/*** @brief  This function handles CAN1 RX0 interrupt request.* @param  None* @retval None*/void CAN1_RX0_IRQHandler(void){int i;CanRxMsg RxMessage = {0};Message rxm = {0};CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);// Drop extended framesif(RxMessage.IDE == CAN_ID_EXT) //不处理扩展帧return;rxm.cob_id = RxMessage.StdId;if(RxMessage.RTR == CAN_RTR_REMOTE)//远程帧rxm.rtr = 1;rxm.len = RxMessage.DLC;for(i=0 ; i<rxm.len ; i++)rxm.data[i] = RxMessage.Data[i];canDispatch(co_data, &rxm);//CANopen自身的处理函数,因为快速SDO不需要反馈,所以在上边处理后就不需要调用这步了}

can1.h

#ifndef __CAN1_H
#define __CAN1_H#include "sys.h"
#include "main.h"
#include "data.h"
// CAN bus defines for cortex-M4 STM32F407#define CANx                       CAN1
#define CAN_CLK                    RCC_APB1Periph_CAN1
#define CAN_RX_PIN                 GPIO_Pin_11
#define CAN_TX_PIN                 GPIO_Pin_12
#define CAN_GPIO_PORT              GPIOA
#define CAN_GPIO_CLK               RCC_AHB1Periph_GPIOA
#define CANx_RX0_IRQn               CAN1_RX0_IRQn#define GPIO_AF_CANx               GPIO_AF_CAN1
#define CAN_RX_SOURCE              GPIO_PinSource11
#define CAN_TX_SOURCE              GPIO_PinSource12unsigned char CAN1_Init(CO_Data * d, uint32_t bitrate);#endif 

timer3.c

#include "timer3.h"TIMEVAL last_counter_val = 0;
TIMEVAL elapsed_time = 0;// Initializes the timer, turn on the interrupt and put the interrupt time to zero
void TIM3_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_IRQChannel = TIM3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);/* Compute the prescaler value */uint16_t PrescalerValue =840-1; //84M频率/840为100k(与timerscfg.h配置一致即可),即10us间隔/* Time base configuration */TIM_TimeBaseStructure.TIM_Period = 65535;TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;TIM_TimeBaseStructure.TIM_ClockDivision = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_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.
void setTimer(TIMEVAL value)
{uint32_t timer = TIM_GetCounter(TIM3);        // Copy the value of the running timerelapsed_time += timer - last_counter_val;last_counter_val = 65535-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.
TIMEVAL getElapsedTime(void)
{uint32_t timer = TIM_GetCounter(TIM3);        // Copy the value of the running timerif(timer < last_counter_val)timer += 65535;TIMEVAL elapsed = timer - last_counter_val + elapsed_time;//printf("elapsed %lu - %lu %lu %lu\r\n", elapsed, timer, last_counter_val, elapsed_time);return elapsed;
}// This function handles Timer 3 interrupt request.
void TIM3_IRQHandler(void)
{//printf("--\r\n");if(TIM_GetFlagStatus(TIM3, TIM_SR_UIF) == RESET)return;last_counter_val = 0;elapsed_time = 0;TIM_ClearITPendingBit(TIM3, TIM_SR_UIF);TimeDispatch();
}

timer3.h

#ifndef __TIMER3_H
#define __TIMER3_H#include "sys.h"
#include "main.h"void TIM3_Init(void);#endif 

2022年3月18日记:
定时器实现函数存在缺陷,当超过一个功能需要调用时间时,会存在干涉。各位如果除了心跳报文发送之外,没用到其他需要时间的功能(节点掉线检测/pdo之类),那么可以忽略。不然可以看一下这个CANopen补充–时间计算出错。

3.3 建立词典


我们起名字为Master,使用心跳管理,这样我们待会便可以通过心跳报文来判断移植成功与否。

在字典里设置心跳报文间隔为1000ms(0x3E8)。这样,它每隔1000ms就会发送一个心跳报文。

点击保存,将生成的.od文件放入CANopen/dictionnary文件夹。

再点击建立词典,同样将生成的.c文件放入CANopen/dictionnary文件夹。

效果如下:

文件 说明
.od文件 词典工程文件,用于配置,不会被工程调用
.c .h 词典文件对应的c和h文件。需要被工程调用

3.4工程配置

文件都弄好了,我们打开keil软件,将这些文件都加入到工程。

3.41 c文件添加

在Groups里新建两个文件夹。需要说明的时候,为了美观,这里把词典文件和外设驱动文件放在一起了。

文件夹 说明
CANopen 含CANopen/src
CANopen_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的初始化

#include "sys.h"
#include "main.h"     int main(void)
{ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4delay_init(168);     //初始化延时函数TIM3_Init();USART1_Init(115200);CAN1_Init(&Master_Data,1000000);unsigned char nodeID = 0x00;                   //主站IDsetNodeId(&Master_Data, nodeID);setState(&Master_Data, Initialisation);       //节点初始化setState(&Master_Data, Operational);        while(1){delay_ms(1000);}
}

下载,启动!

使用软件观察。

心跳没有问题,nice
如果大家有需要让主站检测节点是否掉线的需要,可以看CANopen补充–主站检测节点是否在线。

4 末尾

​ 到这里便移植成功啦。下一篇教程基于STM32F4的CANopen快速SDO通信(超级详细)

基于STM32F4的CANOpen移植教程(超级详细)相关推荐

  1. 基于STM32F4的CANopen快速SDO通信(超级详细)

    基于STM32的CANopen快速SDO通信 前言 1 快速sdo介绍 2 工程配置 2.1 主站词典配置 2.2节点词典配置 3 快速SDO调用代码 4 末尾 本专题相关教程: 基于STM32F4的 ...

  2. 本机php环境搭建教程:windows环境下wampserver的配置教程——超级详细

    转载自:http://youchunyan5.blog.163.com/blog/static/5896062020123474456352/ 本机php环境搭建教程:windows环境下wampse ...

  3. TL437x-IDK基于AM437x的OpenCV移植教程

    1基于AM437x的OpenCV移植 表 1 开发板型号 是否支持本实验 TL437x-EVM 支持 TL437x-EasyEVM 支持 TL437x-IDK 支持 TL437xF-EVM 支持 AM ...

  4. 【LiteOS】STM32F103-LiteOS移植教程(详细篇)

    总览 本文基于STM32F103C8T6,详细讲述华为LiteOS的移植过程.开发工具是MDK5.LiteOS官方已经适配过cortex M系列内核的单片机,因此移植过程非常简单. LiteOS有两种 ...

  5. STM32F4+FreeRTOS+FreeRTosTcpIp移植教程

    花了几天时间完成了FreeRTOS自带的TCP/IP协议栈在stm32F407上的移植,在此记录并分享,第一次写这个,写的不好的地方见谅. 硬件是stm32F407最小系统(内带phy控制器),所以还 ...

  6. stm32f103移植python_【LiteOS】STM32F103-LiteOS移植教程(详细篇)

    总览 本文基于STM32F103C8T6,详细讲述华为LiteOS的移植过程.开发工具是MDK5.LiteOS官方已经适配过cortex M系列内核的单片机,因此移植过程非常简单. LiteOS有两种 ...

  7. 码神之路博客部署教程【完整版】|基于Linux的Docker部署教程|非常详细

    说明 前记:最近跟着哔站码神之路做了一个SpringBoot练手项目,第一次操作碰到了很多困难和问题,尤其是在部署部分,走了很多弯路,这里写下自己的部署过程,供大家参考,也欢迎大家提出宝贵的意见. 哔 ...

  8. 码神之路博客部署教程【完整版】基于Linux的Docker部署教程非常详细

    说明 前记:最近跟着哔站码神之路做了一个SpringBoot练手项目,第一次操作碰到了很多困难和问题,尤其是在部署部分,走了很多弯路,这里写下自己的部署过程,供大家参考,也欢迎大家提出宝贵的意见. 哔 ...

  9. python详细下载安装教程-Python下载并安装图形教程[超级详细]

    现在python语言非常热门,许多小伙伴正在学习python,但是许多小伙伴在安装python时遇到问题. 下面我们要详细介绍下载和安装python的方法. 1. 打开python下载链接,然后单击所 ...

最新文章

  1. AVL树、splay树(伸展树)和红黑树比较
  2. Django进阶之session
  3. opencv_contrib4.4安装
  4. 当我谈 HTTP 时,我谈些什么?
  5. python 把多个list合并为一个并去重内容_110道Python面试题(上)
  6. [Android] SharedPreference的使用
  7. C++builder Tokyo 调用com 不正确的变量类型
  8. CodeM2018复赛
  9. 芯片国产化进程提速 赶超洋品牌核心技术尚欠火候
  10. debug error/runtime error的原因之一
  11. 如何安装Chrome OS系统
  12. websocket ping pong
  13. 【全套完结】电磁场与电磁波实验-----全套Matlab仿真实验
  14. 科学计算机统计说明书,科学计算器的使用方法
  15. rasp 系统_浅谈RASP技术攻防之基础篇
  16. 严昊:25岁接手世界500强企业,公司一年净赚80亿
  17. 【征文大赛】TiDB 社区第二届征文大赛,一次性带走社区全部新周边,还有bose 降噪耳机、倍轻松按摩仪等你拿!
  18. 微信小程序注册/登陆,若依后台获取token
  19. 一分钟读懂PDCA 循环
  20. 解决tomcat 静态页面(html)中文乱码终极篇

热门文章

  1. 计算机网络原理ip计算,计算机网络原理IP地址计算题
  2. Ubuntu 商店无法安装应用
  3. Hygon C86 7xxx处理器在Windows 10下无法开启虚拟化支持的问题
  4. 扎克伯格“致敬”微信,但Facebook Pay能像微信支付一样成功吗?
  5. C语言编写一个四位数的和,c语言编写一段程序,输入一个四位数,输出各位数字的和...
  6. android 扫描手机内存和SD卡,获取手机的视频、音频文件。把获取不到的文件扫描出来
  7. MySQL中用户订单复购率的计算
  8. 直接从Google Play下载apk(附源码)
  9. css实现3d正方体旋转
  10. windows10 1050ti vs2015 openc3.2 cuda8.0配置自己的darknetyolov3