本专题相关教程:
基于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频率。剩下都配置为500K
CAN_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;
else
TxMessage.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; // error
return 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 frames
if(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_PinSource12
unsigned 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 timer
elapsed_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 timer
if(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);//设置系统中断优先级分组4
delay_init(168); //初始化延时函数
TIM3_Init();
USART1_Init(115200);
CAN1_Init(&Master_Data,1000000);
unsigned char nodeID = 0x00; //主站ID
setNodeId(&Master_Data, nodeID);
setState(&Master_Data, Initialisation); //节点初始化
setState(&Master_Data, Operational);
while(1)
{
delay_ms(1000);
}
}
下载,启动!
使用软件观察。
心跳没有问题,nice
如果大家有需要让主站检测节点是否掉线的需要,可以看CANopen补充–主站检测节点是否在线。
4 末尾
到这里便移植成功啦。下一篇教程基于STM32F4的CANopen快速SDO通信(超级详细)文章来源:https://www.toymoban.com/news/detail-432892.html
文章来源地址https://www.toymoban.com/news/detail-432892.html
到了这里,关于基于STM32F4的CANOpen移植教程(超级详细)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!