canFestival移植(2)
前序
前面一篇文章后,其实基本上就可以让canFestival运行起来了,但是移植过程中,你可能还有以下萌币:
(1)“核心层”中的timer.c到底有什么作用?跟drivers下面的硬件定时器到底是怎么交互的?
(2)“核心层”怎么使用到drivers下面的can硬件接口?
(3)怎么生成对象字典啊?应用层应该怎么使用呀?
(4)用户业务逻辑咋设计啊?
通过这篇文章,大家会进一步加强移植的信心。
“核心层”time.c分析
(1)TimeDispatch()函数分析。分析的大部分注释放在源码里面。
void TimeDispatch(void)
{
TIMER_HANDLE i;
TIMEVAL next_wakeup = TIMEVAL_MAX; /* 下一次定时周期 */
/* 定时中断产生后,定时器的计数又增加了多少,一般是0 */
UNS32 overrun = (UNS32)getElapsedTime();
/* total_sleep_time记录的是本次定时时长 */
TIMEVAL real_total_sleep_time = total_sleep_time + overrun;
s_timer_entry *row;
for(i=0, row = timers; i <= last_timer_raw; i++, row++)
{
if (row->state & TIMER_ARMED) /* if row is active */
{
/* 根据row->val判断软定时器timers是否超时,超时就要触发软定时的回调函数 */
if (row->val <= real_total_sleep_time) /* to be trigged */
{
/* 如果row不是周期定时器,直接标记为 TIMER_TRIG */
if (!row->interval) /* if simply outdated */
{
row->state = TIMER_TRIG; /* ask for trig */
}
else /* or period have expired */
{
/* 如果是周期定时器的话,就把row->interval值赋值给row->val,用于下一次超时判定 */
row->val = row->interval - (overrun % (UNS32)row->interval);
row->state = TIMER_TRIG_PERIOD; /* ask for trig, periodic */
/* 找出下一次要触发的定时器 */
if(row->val < next_wakeup)
next_wakeup = row->val;
}
}
else
{
/* 如果此软定时器没有超时,就给它减去刚刚定时的时间 */
row->val -= real_total_sleep_time;
/* Check if this new timer value is the soonest */
if(row->val < next_wakeup)
next_wakeup = row->val;
}
}
}
/* 记录下一次超时需要的时间 */
total_sleep_time = next_wakeup;
/* 设置下一次超时时间 */
setTimer(next_wakeup);
/* 找出要触发的软定时器,并且调用回调 */
for(i=0, row = timers; i<=last_timer_raw; i++, row++)
{
if (row->state & TIMER_TRIG)
{
/* 如果不是周期定时器,那么state就会被修改为0,下次就不会触发 */
row->state &= ~TIMER_TRIG;
if(row->callback)
(*row->callback)(row->d, row->id); /* trig ! */
}
}
}
a)从分析中可知,如果想设计一个基于硬件定时器的软定时器必须要把s_timer_entry timers[MAX_NB_TIMER]结构体中的state设置为TIMER_ARMED。
b)软定时还分为oneshot模式和周期模式。oneshot模式只执行一次,周期模式是周期执行(也就是不听的oneshot)。由于每次报文是应答模式,所以canFestival报文发送使用oneshot模式。周期模式时候,软定时器结构图中的interval 成员记录的是周期的时长,如果到期后,会把interval 值重新写到val成员,同步报文、心跳报文会使用定时器的周期模式。
c)软定时器通过setTimer()函数设置下一次超时时间,但是并没有单位,只是传入参数,具体的单位由用户自己设计,但是最大的超时时间肯定是不能超出硬件的限制。
问题:硬件定时器每次设计定时器的周期到底是要多少啊?
答:硬件定时器主要用于响应超时,从报文超时的宏中可以看出他的超时设置函数setTimer()传入的是300参数,所以,到底要设置多少超时时间你可以决定,比如300ms等。
d)TimeDispatch(void)函数并没有在核心层调用,所以用户必须在硬件定时器的中断函数中调用,在AT91的硬件定时器中断函数就是有调用。
(2)处理函数之外,用户还需要提供设置启动定时器函数和获取定时器到期后计数器又增加的值(一般是0,用户也可以直接return 0,个人觉得影响不大)。设置定时时间“核心层”也不会提供单位,都是用户自定义。
(3)**设置定时时间“核心层”并没有提供单位,用户需要自行设计 **。软定时器只是提供定时的值,并不提工定时的单位,当然用户可以默认设置成ms为单位。
注:“核心层”提供的定时器单位是us,单位大家当然也可以自行设计的。
“核心层”masterSendNMTstateChange()函数分析
(1)masterSendNMTstateChange()函数源码。分析也放入注释中。
UNS8 masterSendNMTstateChange(CO_Data* d, UNS8 nodeId, UNS8 cs)
{
Message m;
MSG_WAR(0x3501, "Send_NMT cs : ", cs);
MSG_WAR(0x3502, " to node : ", nodeId);
/* 组织报文结构体的包 */
m.cob_id = 0x0000; /*(NMT) << 7*/
m.rtr = NOT_A_REQUEST;
m.len = 2;
m.data[0] = cs;
m.data[1] = nodeId;
/* 调用cansend函数把报文发送出去 */
return canSend(d->canHandle,&m);
}
masterSendNMTstateChange()函数其实非常的简单,构建一个报文后,调用硬件提供的canSend()函数把报文发送出去即可。这么简单,还用分析?答:分析的意义就是,每个报文发送“核心层”会组织好报文的帧,然后调用硬件提供的can帧发送接口把帧发送出去即可。
(2)其他报文帧是怎么收发处理的呢?这个需要根据用户是主站还是从站来说的。
a)网络管理报文是主站发出的,而且也不需要从站发送回复报文。所以主站只需要知道报文是否发送成功。
b)sdo报文是一种发送->应答方式,不论主站还是从站发送报文后,都需要有超时应答机制。
c)pdo报文如果不是被主动请求发送的话,就与网络管理报文相同,如果是被主动请求的话,就与sdo发送->应答方式相同。
canDispatch()函数原理概述
在AT91的帧接收中断中,如下图可以看到一个非常关键的函数canDispatch(),此函数就是来解析收到各种不同类型的不同报文帧,然后跟进canopen协议进行解析和处理。如果详细的分析此函数就需要把canopen协议先分析的比较彻底,所以不作为本文的重点部分。
对象字典生成以及结构体资源的意义
(1)对象字典生成工具的环境搭建和使用参考:
https://blog.csdn.net/kezunhb/article/details/90522940,当然也可以直接在历程的基础上进行一些修改。
注:由于python和工具版本不匹配可能导致无法正常启动工具,所以最好就使用链接对应的包即可。
环境安装好之后,我们就可以用对象字典生成工具生成对象字典,在AT91历程里面有如下文件:
(2)资源结构体解析,部分结构体成员如下图,
这个结构体不需要用户进行初始化,在对象字典工具生成c文件的时候,会进行初始化赋值,用户只需要找到这个资源即可,在AT91的对象字典的ObjDict.c文件中最下面就有这个,
上一篇我们知道在初始化的时候就有调用,这里里面的参数就是ObjDict.c文件中自动生成的的结构体,
在ObjDict.c文件中ObjDict_objdict数组记录的就是所有生成的对象字典,每一项都是描述一个对象字典,
在ObjDict.c文件中还有这两个结构图可以特殊的关注一下,
这两个结构体就是只是这些参数在数组中的开始和结束位置,所以用户可以在对象字典中多设置几个cobid的条目数,来让用户一个MCU来当作几个CAN设备使用。
用户业务逻辑设计关键
(1)确定目标,就是说你设计这个产品是干什么用的,也就是说你想干什么,你是想当master进行其他站点管理提供服务还是你想当slave对master进行操作呢?
(2)跟进目标从“核心层”找出你需要用到的函数,在合适的业务逻辑进行调用使用。那么问题又来了,各种报文应该怎么调用不同的函数呢?
(3)怎么设计一个比较好的用户业务逻辑呢?
答:a)在没有操作系统的MCU上面,收到报文后不要在中断里面进行报文解析,毕竟是有一些浪费时间的,可以设计一个全局变量,最终把收到的报文在main函数里面进行解析。
b)在有操作系统的MCU上面,我们可以设计一个报文解析的线程,进行阻塞休眠,当产生收到报文中断后发送信号,让解析报文线程唤醒进行报文解析。
核心思想总结
(1)在发送报文的时候,会调用can硬件接口提供的canSend()函数,同时会启动定时器,进行超时返回进行监控。
(2)在收到报文的时候,会在can硬件中断中调用canReceive()函数收取报文,然后把报文通过canDispatch()函数进行解析。
(3)硬件定时器的主要作用就是报文超时应答使用。