OpenEdv-开源电子网
标题: CANOPEN协议栈的移植以及使用 [打印本页]
作者: whj467467274672 时间: 2017-12-31 16:27
标题: CANOPEN协议栈的移植以及使用
这是一篇入门帖,高手请指点一下,主要是记录我对CANOPEN协议的理解以及使用。图片还没研究好怎么上传,等后面研究好了补上。第一步;新建一个工程,配置好各种时钟之类的。当然你用的是原子的板子, 你也可以拿一个LED实验或者蜂鸣器实验的工程 直接来改,你把其他的注释掉就好了。
第二步;到网上下载CanFestival源码CanFestival-3-10,解压出来,并将文件夹名字改为CanFestival-3-10。移植需要用到的源文件在CanFestival-3-10\src目录下,头文件在CanFestival-3-10\include目录下。
第三部;开始移植。
作者: whj467467274672 时间: 2017-12-31 16:36
本帖最后由 whj467467274672 于 2017-12-31 16:37 编辑
移植步骤:步骤一;在新建好的工程目录下新建文件夹CanFestival,再在CanFestival下新建文件夹driver、inc和src,再在inc文件夹下面新建stm32文件夹。
步骤二;将CanFestival-3-10\src目录下的dcf.c、emcy.c、lifegrd.c、lss.c、nmtMaster.c、nmtSlave.c、objacces.c、pdo.c、sdo.c、states.c、sync.c、timer.c共12个文件拷贝到 CanFestival\src目录下;将CanFestival-3-10\include目录下的所有.h文件共19个文件全部拷贝到CanFestival\inc目录下,再把CanFestival-3-10\examples\AVR\Slave目录下 的ObjDict.h文件拷贝过来,一共20个;将CanFestival-3-10\include\AVR目录下的applicfg.h、canfestival.h、config.h、timerscfg.h共4个头文件拷贝到c anfestival\inc\stm32 目录下;将CanFestival-3-10\examples\TestMasterSlave目录下的TestSlave.c、TestSlave.h、TestMaster.h、TestMaster.c拷贝到canfestival\driver目 录下,并在该目录下新建 stm32_canfestival.c文件。
步骤三;将CanFestival\src目录下的所有.c文件添加到工程;将canfestival\driver目录下的stm32_canfestival.c文件添加到工程;如果实现的是从设备,再将 canfestival\driver目录下的TestSlave.c文件添加到工程,如果实现的是主设备,则将TestMaster.c文件添加到工程。
步骤四;将文件目录canfestival\inc、canfestival\inc\stm32、canfestival\driver等路径添加到工程包含路径。
步骤五;在stm32_canfestival.c中包含头文件#include "canfestival.h",并定义如下函数:
void setTimer(TIMEVAL value)
{
}
TIMEVAL getElapsedTime(void)
{
return 1;
}
unsigned char canSend(CAN_PORT notused, Message *m)
{
return 1;
}
为什么这里要定义几个空函数呢?因为空函数编译的时候不会报错啊,我们现在把后面的都弄好了,最后来处理这边会更方便。我这只是针对新手,老鸟无视就好了。
这几个函数都是定义来供canfestival源码调用的,如果找不到这几个函数编译就会报错。
步骤六;通过以上几步,所有的文件都弄齐了,但是编译一定会出现报错,注释或删除掉config.h文件中的如下几行就能编译通过:
#include <inttypes.h>
#include <avr\io.h>
#include <avr\interrupt.h>
#include <avr/pgmspace.h>
#include <avr\sleep.h>
#include <avr\wdt.h>
如果还有其他错误,可能就是不同版本的源码导致的,也有可能是不同的DEMO引起的,不能解决这些问题的话,那就是GAMEOVER,如果能解决,那么我们继续。
作者: whj467467274672 时间: 2017-12-31 16:45
通过以上步骤处理掉所有错误,对于源码的操作已经结束了,下面就是我们的底层的操作了。
接下来实现刚才定义的3个空函数,函数void setTimer(TIMEVAL value)主要被源码用来定时的,时间到了就需要调用一下函数TimeDispatch(),函数TIMEVAL getElapsedTime(void)主要被源码用来查询距离下一个定时触发还有多少时间。
我们在stm32_canfestival.c文件里定义几个变量如下:
unsigned int TimeCNT=0;//时间计数
unsigned int NextTime=0;//下一次触发时间计数
unsigned int TIMER_MAX_COUNT=70000;//最大时间计数
static TIMEVAL last_time_set = TIMEVAL_MAX;//上一次的时间计数
setTimer和getElapsedTime函数实现如下:
void setTimer(TIMEVAL value)
{
NextTime=(TimeCNT+value)%TIMER_MAX_COUNT;
}
TIMEVAL getElapsedTime(void)
{
int ret=0;
ret = TimeCNT> last_time_set ? TimeCNT - last_time_set : TimeCNT + TIMER_MAX_COUNT - last_time_set;
last_time_set = TimeCNT;
return ret;
}
另外还要开一个1毫秒的定时器,每1毫秒调用一下下面这个函数。
void timerForCan(void)
{
TimeCNT++;
if (TimeCNT>=TIMER_MAX_COUNT)
{
TimeCNT=0;
}
if (TimeCNT==NextTime)
{
TimeDispatch();
}
}
下面我贴上定时器的初始化代码
void TIM3_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseInitStructure.TIM_Period = 1000-1;
TIM_TimeBaseInitStructure.TIM_Prescaler= 84-1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM3,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //òç3öÖD¶Ï
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
// TimerForCan();
TimeCNT++;
if (TimeCNT>=TIMER_MAX_COUNT)
{
TimeCNT=0;
}
if (TimeCNT==NextTime)
TimeDispatch();
}
}
作者: whj467467274672 时间: 2017-12-31 16:49
上面讲过了定时器,下面就是CAN的发送与接收函数。
u8 CAN1_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);
CAN_InitStructure.CAN_TTCM=DISABLE;
CAN_InitStructure.CAN_ABOM=DISABLE;
CAN_InitStructure.CAN_AWUM=DISABLE;
CAN_InitStructure.CAN_NART=ENABLE;
CAN_InitStructure.CAN_RFLM=DISABLE;
CAN_InitStructure.CAN_TXFP=DISABLE;
CAN_InitStructure.CAN_Mode= mode;
CAN_InitStructure.CAN_SJW=tsjw;
CAN_InitStructure.CAN_BS1=tbs1;
CAN_InitStructure.CAN_BS2=tbs2;
CAN_InitStructure.CAN_Prescaler=brp;
CAN_Init(CAN1, &CAN_InitStructure);
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=CAN_Filter_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
return 0;
}
void CAN1_RX0_IRQHandler(void)
{
u32 i;
Message m;
CanRxMsg RxMessage;
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
m.cob_id=RxMessage.StdId;
if(RxMessage.RTR == CAN_RTR_REMOTE)
m.rtr=1;
else if(RxMessage.RTR == CAN_RTR_DATA)
m.rtr=0;
m.len=RxMessage.DLC;
for(i = 0; i < RxMessage.DLC; i++)
m.data[i] = RxMessage.Data[i];
canDispatch(&TestSlave_Data, &m);
}
unsigned char canSend(CAN_PORT notused, Message *m)
{
uint32_t i;
CanTxMsg TxMessage;
CanTxMsg *ptx_msg=&TxMessage;
ptx_msg->StdId = m->cob_id;
if(m->rtr)
ptx_msg->RTR = CAN_RTR_REMOTE;
else
ptx_msg->RTR = CAN_RTR_DATA;
ptx_msg->IDE = CAN_ID_STD;
ptx_msg->DLC = m->len;
for(i = 0; i < m->len; i++)
ptx_msg->Data[i] = m->data[i];
if( CAN_Transmit( CAN1, ptx_msg )==CAN_NO_MB)
{
return 0xff;
}
else
{
return 0x00;
}
}
作者: whj467467274672 时间: 2017-12-31 16:55
最后一步就是main函数怎么启动了。
unsigned char nodeID=0x07; 这个是你给你移植好的设备设置的站号
extern CO_Data TestSlave_Data; 这个是后面讲到对象字典了再告诉讲
extern UNS8 TEST[8]; 这个是后面讲到对象字典了再告诉讲
int main(void)
{
delay_init(168);
CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,CAN_Mode_Normal);
setNodeId(&TestSlave_Data, nodeID);
setState(&TestSlave_Data, Initialisation);
setState(&TestSlave_Data, Operational);
TIM3_Init();
while(1)
{
}
}
setState(&TestSlave_Data, Operational); 看过CANOPEN协议的朋友应该知道这是什么意思?而我为什么直接程序让他启动了呢,是因为第一次做希望上电就能收到报文,所以写了一个这样,不需要的可以直接注释掉。
作者: whj467467274672 时间: 2017-12-31 16:56
本帖最后由 whj467467274672 于 2017-12-31 17:02 编辑
后面我来继续讲怎么使用对象字典。但是看后面的内容,还是建议一定熟读CANOPEN协议了之后再尝试,不然可能会看不懂。还有就是我要研究一下怎么发图片,不然一会可能说不清楚。还得安装3个软件,Beremiz-1.1_rc5 python-2.7.10(必须是2.7版本) wxpython2.8 其中python2.7安装好了还有一个设置环境变量,这里就不说的太细了,不然你们什么都不查,自己就不懂脑经了。
作者: whj467467274672 时间: 2017-12-31 19:27
安装好上面几个程序之后,打开C:\Program Files (x86)\Beremiz\CanFestival-3\objdictgen\objdicttedit.py 打开方式选择python。如下图1.你可以新建,如果你其他的EDS文件,你也可以直接打开。打开之后的界面如图2所示。可以看到,我选择了0x1000-0x1029,下面选择的是 1017,那么右边有一个写值0x64的话,就是100ms发送一次心跳。其他的PDO就自己慢慢研究吧。
作者: whj467467274672 时间: 2017-12-31 19:30
在移植完毕使用之后发现几个问题。第一个就是定时器是一个动态值,第二个就是事件发送不能发送,不知道是什么问题。忘高手路过指点一下。
作者: whj467467274672 时间: 2017-12-31 19:31
另外工程暂时不发在论坛了,我觉得上面已经说得非常详细了,如果还有其他问题可以论坛交流。
作者: magicoctoy 时间: 2018-1-2 16:00
收藏!
作者: 516182660 时间: 2018-2-3 18:55
不知道能否给我一份canopen源代码,谢谢,邮箱516182660@qq.com
作者: whj467467274672 时间: 2018-2-4 10:44
看附件
作者: 雨天Al2 时间: 2018-10-11 08:17
你好!
我有个问题想请教你。
1. 首先是TimeDispatch()这个 函数的工作原理,你能给我简单描述一下吗?
2. 下面这两个定义的作用,为什么要这样定义?能否举个简单的例子,谢谢
// The timer is incrementing every 1 ms.
#define MS_TO_TIMEVAL(ms) ((ms)) //change @bruce
#define US_TO_TIMEVAL(us) ((us)/1000) //change @bruce
作者: asdf89 时间: 2018-10-12 09:54
收藏了,谢谢楼主
作者: whj467467274672 时间: 2018-10-14 10:47
本帖最后由 whj467467274672 于 2018-10-15 09:28 编辑
TimeDispatch我的理解是使用定时器来处理各个节点的状态,功能是遍历timers,如果有超时的,就调用相应的call, 这些timers是由SetAlarm()函数来设定的
#define TIMEVAL_MAX 0XFFFFFF(定时器的最大计数值, 24位为0xFFFFFF)
#define MS_TO_TIMEVAL(ms) (1ms对应的计数值, 例如计数频率为1us,则此值为1000)
#define US_TO_TIMEVAL(us) (1us对应的计数值, 例如计数频率为1us,则此值为1)
作者: 雨天Al2 时间: 2018-10-16 08:18
谢谢你的回答。 我在移植的时候遇到一个问题,上电后,板子按照我设置的心跳时间给我发送状态,但是我改变状态后,其心跳时间就变了,
假如我设置的心跳是1S,那么在1S内就会发送两次心跳,这明显是不对的,请问这个可能造成这种现象的原因都有哪些?
作者: whj467467274672 时间: 2018-10-16 20:50
我也不是特别懂协议栈底层。我忘记在哪里看到的,是说协议栈本身的时间机制,不是一个特别精准,而是一个动态调节的时间机制。
作者: 雨天Al2 时间: 2018-10-16 21:28
谢谢你的回复,我在继续找找问题
作者: whj467467274672 时间: 2018-10-17 08:24
找到答案了可以给我科普一下吗?
作者: 雨天Al2 时间: 2018-10-17 19:56
本帖最后由 雨天Al2 于 2018-10-17 19:59 编辑
今天又找了一天,其实问题就是出在getElapsedTime()函数这个地方,
last_time_set=TimeCNT;不应该放在这里。应该把这句话放在函数TimeDispatch();的前面
作者: 雨天Al2 时间: 2018-10-17 20:04
我看网上也有问这个问题的
https://www.a_m_o_bbs.com/thread-5688488-1-1.html?_dsign=e79ff9a5
链接上的代码试了,有效。不过方式不是太一样,效果是一样的
作者: whj467467274672 时间: 2018-10-18 09:40
本帖最后由 whj467467274672 于 2018-10-18 09:43 编辑
原子论坛把阿mo论坛的链接屏蔽了吗?上面的地址要把a_m_o_bbs中间的横线去掉才是正确的链接地址
阿mo论坛的资料无法下载是硬伤。
作者: asdf89 时间: 2018-10-18 10:12
谢谢楼主分享
作者: DOTHAT202 时间: 2018-11-24 17:05
/* 功能:设置定时器在value微妙之后触发 */
void setTimer(TIMEVAL value)
{
uint16_t capture = 0;
capture = htim1.Instance->CNT; /* 计数器值 */
htim1.Instance->ARR = capture + value; /* 自动重载寄存器 */
}
/* 获取定时器当前计数值 */
TIMEVAL getElapsedTime(void)
{
uint16_t timer = htim1.Instance->CNT; /* 计数器值 */
timer = timer - last_time_set;
return timer;
}
/* 定时器回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
switch((uint32_t)htim->Instance)
{
case (uint32_t)TIM1:
last_time_set = htim1.Instance->CNT;
TimeDispatch();
break;
default:
break;
}
}
作者: NewGuard 时间: 2019-1-2 13:15
请问下,你发出来的canfestival移植源文件中为何找不到TestSlave.c和TestMaster.c文件,网上下的版本也是找不到这两个文件。或者有这两个文件的话前面的文件又会缺少,望指点下!
作者: whj467467274672 时间: 2019-1-3 08:54
本帖最后由 whj467467274672 于 2019-1-3 08:55 编辑
这个就是对象字典文件了,要自己来实现。你需要一个canopen对象字典编辑器
作者: NewGuard 时间: 2019-1-3 09:22
谢谢我昨天偶然间搞定了,objdictedit.py把TestMaster.od生成词典TestMaster.c就可以了,我这边移植完后感觉没怎么用上canfestival的感觉,只用TimeDispatch()就行了吗?我是有点迷茫,可否指点下?
作者: whj467467274672 时间: 2019-1-3 14:31
canDispatch()和TimeDispatch()都要用上
作者: NewGuard 时间: 2019-1-3 16:24
1.TimeDispatch()在ms定时器里调用就需要管理了吧
2.canDispatch()怎么用,怎么才能和固件CAN的发送和接收联系到一起
3.我只有一块F1板子,回环模式能测试到效果吗?
作者: whj467467274672 时间: 2019-1-3 16:40
canDispatch()需要放在CAN中断接收里面,通过这个函数把接收的数据发送给协议栈处理。
只有一个F1的板子肯定是不行的,你遵守了CANOPEN协议,那么你就应该把数据发送出来,通过USB_CAN来观察报文。
作者: NewGuard 时间: 2019-1-3 17:58
//中断接收函数
//USB_LP_CAN1_RX0_IRQHandler->HAL_CAN_IRQHandler->CAN_Receive_IT->HAL_CAN_RxCpltCallback
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* hcan)
{
__HAL_CAN_CLEAR_FLAG(hcan,CAN_IT_FMP0); //清除标志位
HAL_CAN_Receive_IT(hcan, CAN_FIFO0); //重新开启接收中断
__HAL_CAN_ENABLE_IT(hcan,CAN_IT_FMP0); //使能中断
can_msg.cob_id = hcan->pRxMsg->StdId;
can_msg.rtr = hcan->pRxMsg->RTR;
can_msg.len = hcan->pRxMsg->DLC;
for(uint8_t i =0; i < 8; i++)
can_msg.data = hcan->pRxMsg->Data;
canDispatch(&TestMaster_Data, &can_msg); //处理消息
//printf("HAL_CAN_RxCpltCallback
ata[0]= 0x%X,Data[1]=0x%X \r\n",hcan->pRxMsg->Data[0],hcan->pRxMsg->Data[1]);
UART_Mixed_TxTrigger(&muart1,(char *)hcan->pRxMsg->Data,hcan->pRxMsg->DLC);
}
我大概就这么调用的,回头买个USBCAN调试工具再看看情况吧
作者: whj467467274672 时间: 2019-1-4 09:02
你这个接收回调函数吧,大概看了下没问什么问题, 你可以先测试一下。你这里是不是应该做一下清除定时器的中断呢?
作者: NewGuard 时间: 2019-1-4 09:26
不太明白,清除定时器什么意思?TimeDispatch()吗?
作者: whj467467274672 时间: 2019-1-4 09:52
TimeDispatch()是需要在硬件定时器里面定时调用的。
作者: 二九结狐六体 时间: 2019-1-4 09:58
可以的,好好学一下!!!
作者: tonghan2009 时间: 2019-1-6 23:11
你好,请教一下,我用Objdictedit来生成字典,为什么我的PDO的映射参数表里面无法映射?只有毫无和Error Register两个选项
作者: whj467467274672 时间: 2019-1-8 08:39
因为可以映射的地址你没有配置
作者: tonghan2009 时间: 2019-1-9 13:47
嗯嗯,确实是,谢谢
作者: NewGuard 时间: 2019-1-9 17:07
1.首先谢谢楼主的无私帮助
2.定时器中断我用的是HAL库的延时函数中断,直接在里面timerForCan();
3.买的USBCAN调试工具到了,我测试了下会返回数据(附有图片),但是我设置的节点是0x08
为什么发给我的确实0x388和0x708不太明白?
4.然后心跳包是只有主机发送吗?我用的从机为啥没有数据,0x1017也配置了具体值
5.这样canopen算跑起来了吗?具体的接收数据处理是不是还需要自己另外操作,还是只要字典里面修改就可以了,我到现在还是迷茫到要死。
再次感谢楼主!
作者: whj467467274672 时间: 2019-1-9 21:20
这应该算是把协议栈跑起来了,但是距离正在的使用,我觉得还有待学习。
至于为什么这么说,是因为你还不知道COB_id是什么,我简单的说一下,你自己再去查查资料。
0X708就是COB_ID+NODE_ID,你现在应该收到的数据是0x708 05或者0x708 7F。0x708是COB_ID+NODE_ID 05表示节点状态。
canopen没有严格的主从机之分,常常大家说的主机是指具备网络管理的那个节点,你可以把这个功能赋予到任何节点,但是一般只赋予一个,那么他就认为是主机其他的节点都是一样的了。
具体的接收数据你自己再研究一下吧。
有问题欢迎再看过资料之后再问,这样更高沟通。
作者: NewGuard 时间: 2019-1-10 09:54
谢谢!
的确自己应该主动去多看下资料,对自己提高也有益处!
作者: whj467467274672 时间: 2019-1-10 13:16
可以通过USB_CAN能控制LED的亮灭基本上就算会用了
作者: NewGuard 时间: 2019-1-10 16:39
本帖最后由 NewGuard 于 2019-1-10 16:41 编辑
之前接收的内容一个是类似节点上线通知和心跳报文状态,但现在遇到一个奇怪的问题,心跳报文的时间间隔和设置的时间不一样(设置0x64),大概会是什么问题?
作者: NewGuard 时间: 2019-1-10 17:33
本帖最后由 NewGuard 于 2019-1-10 17:39 编辑
另外对接收函数做了处理,不知道是不是这样?如果是正确的话,只需要把相应printf的内容更改为对数据处理就可以了,PC上位机发送的CANID:0x208(PDOrx)、0x188(PDOtx)
其中对PDO的映射不太明白有什么用,还望解惑,谢谢!
作者: whj467467274672 时间: 2019-1-11 09:09
节点上线通知的专业术语叫BOOT UP。 心跳不准的话,你要看下定时器的事。
作者: whj467467274672 时间: 2019-1-11 09:15
打印函数都是错误把,你好好看看,你不要乱改。关于PDO映射的东西,百度一大堆,你先看一下,具体哪一个地方不懂你再问,你这么问,需要说的东西太多了。
作者: NewGuard 时间: 2019-1-14 09:38
本帖最后由 NewGuard 于 2019-1-14 17:57 编辑
感谢回复!
1.心跳包的问题,我用示波器测试了定时器是1ms轮循一次timerForCan()函数,我把函数文件放出来
2.打印函数错误不要乱改,是指void canDispatch(CO_Data* d, Message *m)函数不能修改,但是如何对接收数据处理呢?应该有需要更改的地方吧,比如点灯之类的操作3.TPDO和RPDO都有参数设置和一一映射的关系,对于映射到其它相应地址、子索引和空间占用大小,但是如上我的疑问一样,我还是不太明白具体的处理函数不是我自己
写吗?我只有一个板子和USBCAN上位机,怎么才能做到在协议中去控制LED的开关(底层的处理函数),能指点下思路,我总是想不通这点,谢谢!
uint32_t TimeCNT=0;//时间计数
uint32_t NextTime=0;//下一次触发时间计数
uint32_t TIMER_MAX_COUNT = 70000;//最大时间计数
static TIMEVAL last_time_set = TIMEVAL_MAX;//上一次的时间计数
//源码用来定时的,时间到了就需要调用一下函数TimeDispatch()
void setTimer(TIMEVAL value)
{
NextTime = (TimeCNT+value)%TIMER_MAX_COUNT;
}
//源码用来查询距离下一个定时触发还有多少时间
TIMEVAL getElapsedTime(void)
{
int ret = 0;
ret = (TimeCNT>last_time_set)?(TimeCNT-last_time_set) : (TimeCNT+TIMER_MAX_COUNT-last_time_set);
last_time_set = TimeCNT;
return ret;
}
//没1ms要调用一次下面的函数
void timerForCan(void)
{
TimeCNT++;
if(TimeCNT >= TIMER_MAX_COUNT)
{
TimeCNT=0;
}
if(TimeCNT == NextTime)
{
TimeDispatch();
}
}
//canSend 会被canfestival调用
UNS8 canSend(CAN_PORT notused, Message *m)
{
return CAN1_Send_Msg((Message *) m); //发送CAN消息
}
UNS8 canChangeBaudRate(CAN_PORT port, char* baud)
{
return 0;
}
作者: whj467467274672 时间: 2019-1-15 08:50
第一个心跳的问题,你看下我前面给别人的回答,有个地方需要修改定时器的最大计数器,你看看你用的是不是16位的,这里你改一下看看。
第二个问题,协议栈不需要修改,对接收到的数据进行处理最后都指向了对象字典,你需要做的就是对象字典的操作。
第三个问题,如何控制LED,假如你在对象字典里有一个变量,初始值是00,那么你可以通过发送SDO的方式来修改这个变量的值,你在程序里判断变量不为0的时候LED就点亮。
作者: 正点原子 时间: 2019-1-22 01:50
谢谢分享
作者: 正点原子 时间: 2019-1-22 01:51
cool
作者: NewGuard 时间: 2019-1-23 21:36
头像换了差点没认出来!
1.关于心跳时间不准的问题,在timerscfg.h中有MS_TO_TIMEVAL(ms)和US_TO_TIMEVAL(us)两个宏定义没有修改为对应心跳的ms单位
2.已经可以通过PC端发送相应的数据来改变RPDO中映射到的变量值,即已经可以在程序中来通过变量的值对LED的开关
不过也遇到新的问题需要请教:
1.TPDO映射的数据怎么发送出去?
2.我在从预处理模式切换为操作模式时,会收到TPDO的数据,不过也只有一次,为什么会这样?
3.怎么样才能通过PC端的命令回读到TPDO和RPDO的映射值呢?
非常感谢楼主!
作者: whj467467274672 时间: 2019-1-24 08:58
1,在对象字典里面选择好TPDO映射的数据的位置,比如说索引0x2000 子索引0x01应该就可以了。在多说一点比如说0x1800是TPDO参数那么你在这个里面设置好你想怎么发送TPDO1,然后你需要在0x1a00设置数据映射到哪里,映射的地址就是我前面说的0x2000 0x01,这样应该就能解决你的疑问。
2,你看下的设置的TPDO的发送类型,这个你可以自己看下资料,设置成你需要的状态。
3,PDO叫过程数据,他是主动发送的,板子发过来的对他而言是TPDO,对于PC就是RPDO,你这个现在不理解是因为你还没做2个开发板通讯。你在查第二个问题的时候应该能看到关于第三个问题的答案。
最后,我有小小的请求,你这边整理到的资料可否在这个帖子共享一下。让希望学习CANOPEN的人能更方便的在这个帖子学习。
作者: whj467467274672 时间: 2019-1-24 08:58
1,在对象字典里面选择好TPDO映射的数据的位置,比如说索引0x2000 子索引0x01应该就可以了。在多说一点比如说0x1800是TPDO参数那么你在这个里面设置好你想怎么发送TPDO1,然后你需要在0x1a00设置数据映射到哪里,映射的地址就是我前面说的0x2000 0x01,这样应该就能解决你的疑问。
2,你看下的设置的TPDO的发送类型,这个你可以自己看下资料,设置成你需要的状态。
3,PDO叫过程数据,他是主动发送的,板子发过来的对他而言是TPDO,对于PC就是RPDO,你这个现在不理解是因为你还没做2个开发板通讯。你在查第二个问题的时候应该能看到关于第三个问题的答案。
最后,我有小小的请求,你这边整理到的资料可否在这个帖子共享一下。让希望学习CANOPEN的人能更方便的在这个帖子学习。
作者: superIOT 时间: 2019-1-24 11:52
收藏了,多谢楼主。
作者: NewGuard 时间: 2019-1-24 12:38
我把问题解决后,现在还差PDO的事件发送还没搞定,解决后我发个总结出来
作者: NewGuard 时间: 2019-1-24 14:12
本帖最后由 NewGuard 于 2019-1-24 14:30 编辑
1.看了资料,PDO多用两种类型事件和时间触发发送协议为0x01和0xFE
2.我这边设置了两个板子TPDO参数和映射值,一个是事件触发另一个是时间触发
3.事件触发只有在预操作模式切换为操作模式时返回了一次数据,后面即使通过SDO的方式改变事件触发的变量值,也不会有返回数据的
4.时间触发的PDO根本就没有返回数据,好奇怪
设置参数如下:
作者: whj467467274672 时间: 2019-1-24 14:29
你把发送类型设置成0XFF 看看结果
作者: NewGuard 时间: 2019-1-24 14:35
谢谢回答。修改为0xFF(设备子协议特定事件),好像没有效果。只在第一次模式切换时有数据返回
作者: whj467467274672 时间: 2019-1-24 14:49
/* index 0x1800 : Transmit PDO 1 Parameter. */
UNS8 TLP_ObjDiction_highestSubIndex_obj1800 = 6; /* number of subindex - 1*/
UNS32 TLP_ObjDiction_obj1800_COB_ID_used_by_PDO = 0x180; /* 384 */
UNS8 TLP_ObjDiction_obj1800_Transmission_Type = 0xFF; /* 255 */
UNS16 TLP_ObjDiction_obj1800_Inhibit_Time = 0x0; /* 0 */
UNS8 TLP_ObjDiction_obj1800_Compatibility_Entry = 0x0; /* 0 */
UNS16 TLP_ObjDiction_obj1800_Event_Timer = 0xC8; /* 200 */
UNS8 TLP_ObjDiction_obj1800_SYNC_start_value = 0x0; /* 0 */
ODCallback_t TLP_ObjDiction_Index1800_callbacks[] =
这是我项目中截取的一部分配置。
作者: NewGuard 时间: 2019-1-24 15:31
1.我不确定我是不是找到问题原因了,Inhibit time的单位感觉像是*0.1s,把这个值设为0或者比Event time (*ms)的值小,就可以定时发送出PDO的数据了
2.但事件触发(SDO改变映射值)在Inhibit time和Event time设为0的且协议类型为FE FF情况下,还是无法触发其发出数据,不知道你是怎么解决这个问题的。
同样非常感谢楼主的无私帮助!
作者: whj467467274672 时间: 2019-1-24 16:20
我没用过事件发送,你找到原因了,可以告诉我一下。
作者: NewGuard 时间: 2019-1-26 09:34
本帖最后由 NewGuard 于 2019-1-26 10:00 编辑
我还是没搞定,也发帖了,好像没人指导,暂时先搁置着吧。
另外能否问下,之前都是从机模式,如果是主机模式,大概是一个怎样操作思路,
我看网上说主机检测到从机上线后,SDO配置从机参数、主机PDO的处理这些都
是需要自己写函数吗?我不知道怎么下手,有没有相应的例子给参看下,非常感谢!
另外我把从机的学习之路总结下,希望对有需要的人有些帮助,在此也感谢楼主的无私帮助。
1.我是基于HAL库STM32F407VET6,不过这个也不影响。
2.移植过程基本上都是按楼主的操作,当然编译时有报错的差不多都能解决。
3.另外有两个地方需要修改
3.1 getElapsedTime()函数中的last_time_set = TimeCNT;应放在TimeDispatch()前面,这个也是前面的童鞋找到的问题所在。
3.2 MS_TO_TIMEVAL和US_TO_TIMEVAL两个宏定义,需要修改为对应的中断时间,否则会对你的字典配置参数的ms和us单位有影响
// The timer is incrementing every 1 ms.
#define MS_TO_TIMEVAL(ms) ((ms) * 1)
#define US_TO_TIMEVAL(us) ((us) / 1000)
4.上面完成后,编译没问题就可以作为从机功能使用了,主要是PDO的映射值,前面也有相应的图片,映射值说白了就是一些变量,这些变量就是你需要关心的地方,当然我目前只是从机,作为主机应该会多些内容吧
5.我把程序配置做一些截图,有疑问的我这边可以继续提供些许帮助,当然我有疏忽或错误的地方还望指出!
作者: whj467467274672 时间: 2019-1-26 13:26
本帖最后由 whj467467274672 于 2019-1-26 13:30 编辑
在CANOPEN里面并没有严格的主从之分,更多的是生产者与消费者模型,你并不用过多的关心主从,大家常说的主从是指具有网络管理的那个站点认为是主站。
就算你不是主站你也是可以发送接收PDO,发送SDO。
如果你需要设置主站的话,你在创建对象字典的时候你选择MASTER就好了。但是你会发现他们一样的。你需要使用主站的话,我记得是有一些回调函数可以检测到哪些站点上线发送boot up的信号,剩下的主从就没什么区别了。不过我忘记是哪个回调函数了,你可以自己先找找看,回头我找到了,在这里回帖告诉你。
作者: NewGuard 时间: 2019-1-26 17:17
1.check_and_start_node(d,nodeId);这个函数可以把上线的节点初始化,不过我是在proceedNODE_GUARD()里面添加了这个函数,不知道有没有问题
2.另外就是,如果主机SDO方式对从机配置,此时从机会响应SDO返回数据,我想问的是这个返回数据需要我们处理还是忽略掉,如果忽略的话,主机配置后的结果我们怎么知道,不一定每次都是成功的吧
作者: whj467467274672 时间: 2019-1-28 17:07
1,我还没看,这个你自己试试
2,首先你要知道CAN硬件本身是带校验的,CAN一次最多发送8个字节,你用SD0发送16个字节怎么办?协议栈会自动帮你分帧,没记错的话第一个字节来表示。而且你用sdo对从站进行对写操作都会有返回值,你可以通过返回值来判断。
作者: 墨香余味 时间: 2019-3-28 19:17
你好,楼主大侠!
我用vs新建哪个项目呀,mfc还是32位控制台呀,感觉怪怪的,还有我用MCP2515的芯片哦,也可以来你这里移植吗?
作者: 墨香余味 时间: 2019-3-28 19:52
你好,大侠,你是用说明原件移植楼主的源码呀,/*移植步骤:步骤一;在新建好的工程目录下新建文件夹CanFestival,再在CanFestival下新建文件夹driver、inc和src,再在inc文件夹下面新建stm32文件夹。
步骤二;将CanFestival-3-10\src目录下的dcf.c、emcy.c、lifegrd.c、lss.c、nmtMaster.c、nmtSlave.c、objacces.c、pdo.c、sdo.c、states.c、sync.c、timer.c共12个文件拷贝到 CanFestival\src目录下*/
这是怎么新建的呀
作者: 墨香余味 时间: 2019-3-28 20:10
你好,楼主,这第1步是怎么实现的呀,怎么新建那么多文件夹的呀,vs可以吗
作者: whj467467274672 时间: 2019-3-29 08:56
第一步是说你得有一个可用的工程
作者: 墨香余味 时间: 2019-3-30 08:29
恩,我新建一个MFC的解决方案啊,然后会出现头文件夹和源文件夹以及资源文件夹了。再新建文件夹的话也就是新建解决方案了就和上面的一样撒
作者: 墨香余味 时间: 2019-3-30 08:47
你好,楼主,能具体数说下吗?还有一点我芯片的板子带有can接口,驱动器也带有can接口,然后直接用网线链接了,把您的这个移植过来就可以看到canopen实现过程是吗?
我目前只是把芯片收发数据实现了
作者: whj467467274672 时间: 2019-3-30 10:37
我GET不到你的点,你到底想干什么。我写的有这么难以理解吗?在你前面提问的一个人,也没遇到工程不知道如何建立的问题。
作者: 墨香余味 时间: 2019-3-31 13:40
本帖最后由 墨香余味 于 2019-3-31 14:46 编辑
恩,再试试,好像明白了。
作者: 墨香余味 时间: 2019-3-31 14:12
恩,第1步可以了,可是到第2步最后一句这解压包里没有你说那4个文件呀,名称都对应不上
/*将CanFestival-3-10\examples\TestMasterSlave目录下的TestSlave.c、TestSlave.h、TestMaster.h、TestMaster.c拷贝到canfestival\driver目 录下,并在该目录下新建 stm32_canfestival.c文件。*/
作者: 墨香余味 时间: 2019-3-31 16:36
谢谢,楼主的解答
作者: 墨香余味 时间: 2019-3-31 16:41
你好,大侠!请问下,你是建立的什么工程呀,比如:MFC,智能设备或者32位控制台应用程序
作者: whj467467274672 时间: 2019-4-1 09:43
我这是在STM32上跑的工程,如果你想建立一个在PC上跑的 程序,我就帮不上你了,我没有操作经验
作者: 墨香余味 时间: 2019-4-1 15:55
本帖最后由 墨香余味 于 2019-4-1 16:06 编辑
恩,我知道你的那个是在STM32芯片上跑的工程,也是用vs的呀。只是把板子的固件下载到pc上安装就可以了,可以来收发数据的
作者: 墨香余味 时间: 2019-4-1 16:54
本帖最后由 墨香余味 于 2019-4-1 17:13 编辑
还有一点,我按照你说的步骤1、2但是在我的工程里都不显示那些文件呢?
作者: 墨香余味 时间: 2019-4-1 16:55
后面的我弄了个eds文件可以看到对象字典
作者: 墨香余味 时间: 2019-4-1 18:06
本帖最后由 墨香余味 于 2019-4-1 18:46 编辑
不知道啥原因,错误一大把。。。。
(, 下载次数: 14)