前言
最近一直在做canfestival移植到树莓派pico上的工作。期间,阅读canfestival源码时总能看到一些精彩的代码。本人是大龄嵌入式工作者,记性不太好,而且由于移植工作一直没有完成,一直没有新的文章发出来,所以想着开个专栏,把路过的优秀代码记录下来,毕竟优雅永不过时。
一、canfestival简介
canfestival是一个免费而且开源的CANopen协议栈,工作中需要进阶了解CANopen协议,可以深入阅读CanFestival源码。
二、canfestival状态切换state.c源码
satetes.c实现了CANopen协议状态切换逻辑,以及在不同状态下调用对应的CANopen通信对象功能。CiA DS 301中对CANopen状态切换如下图所示:
在不同状态下,需要实现的CANopen通信对象如下图所示:
2.1 state.c源码
代码如下:
/*
This file is part of CanFestival, a library implementing CanOpen Stack.
Copyright (C): Edouard TISSERANT and Francis DUPIN
See COPYING file for copyrights details.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*!
** @file states.c
** @author Edouard TISSERANT and Francis DUPIN
** @date Tue Jun 5 09:32:32 2007
**
** @brief
**
**
*/
#include "data.h"
#include "sysdep.h"
/** Prototypes for internals functions */
/*!
**
**
** @param d
** @param newCommunicationState
**/
void switchCommunicationState(CO_Data* d,
s_state_communication *newCommunicationState);
/*!
**
**
** @param d
** @param m
**/
#define StartOrStop(CommType, FuncStart, FuncStop) \
if(newCommunicationState->CommType && d->CurrentCommunicationState.CommType == 0){\
MSG_WAR(0x9999,#FuncStart, 9999);\
d->CurrentCommunicationState.CommType = 1;\
FuncStart;\
}else if(!newCommunicationState->CommType && d->CurrentCommunicationState.CommType == 1){\
MSG_WAR(0x9999,#FuncStop, 9999);\
d->CurrentCommunicationState.CommType = 0;\
FuncStop;\
}
#define None
/*!
**
**
** @param d
** @param newCommunicationState
**/
void switchCommunicationState(CO_Data* d, s_state_communication *newCommunicationState)
{
#ifdef CO_ENABLE_LSS
StartOrStop(csLSS, startLSS(d), stopLSS(d))
#endif
StartOrStop(csSDO, None, resetSDO(d))
StartOrStop(csSYNC, startSYNC(d), stopSYNC(d))
StartOrStop(csLifeGuard, lifeGuardInit(d), lifeGuardStop(d))
StartOrStop(csEmergency, emergencyInit(d), emergencyStop(d))
StartOrStop(csPDO, PDOInit(d), PDOStop(d))
StartOrStop(csBoot_Up, None, slaveSendBootUp(d))
}
/*!
**
**
** @param d
** @param newState
**
** @return
**/
UNS8 setState(CO_Data* d, e_nodeState newState)
{
if(newState != d->nodeState){
switch( newState ){
case Initialisation:
{
s_state_communication newCommunicationState = {1, 0, 0, 0, 0, 0, 0};
d->nodeState = Initialisation;
switchCommunicationState(d, &newCommunicationState);
/* call user app init callback now. */
/* d->initialisation MUST NOT CALL SetState */
(*d->initialisation)(d);
}
/* Automatic transition - No break statement ! */
/* Transition from Initialisation to Pre_operational */
/* is automatic as defined in DS301. */
/* App don't have to call SetState(d, Pre_operational) */
case Pre_operational:
{
s_state_communication newCommunicationState = {0, 1, 1, 1, 1, 0, 1};
d->nodeState = Pre_operational;
switchCommunicationState(d, &newCommunicationState);
(*d->preOperational)(d);
}
break;
case Operational:
if(d->nodeState == Initialisation) return 0xFF;
{
s_state_communication newCommunicationState = {0, 1, 1, 1, 1, 1, 0};
d->nodeState = Operational;
newState = Operational;
switchCommunicationState(d, &newCommunicationState);
(*d->operational)(d);
}
break;
case Stopped:
if(d->nodeState == Initialisation) return 0xFF;
{
s_state_communication newCommunicationState = {0, 0, 0, 0, 1, 0, 1};
d->nodeState = Stopped;
newState = Stopped;
switchCommunicationState(d, &newCommunicationState);
(*d->stopped)(d);
}
break;
default:
return 0xFF;
}/* end switch case */
}
/* d->nodeState contains the final state */
/* may not be the requested state */
return d->nodeState;
}
2.1 state.c源码解析:
文章中截出的代码段实现了CANopen状态切设置函数,通过函数 UNS8 setState (CO_Data d, e_nodeState newState)*实现切换到任意状态,以及启动相应通信对象功能。函数原型和参数说明如下:
/**
* @ingroup statemachine
* @brief Change the state of the node
* @param *d Pointer on a CAN object data structure
* @param newState The state to assign
* @return
*/
UNS8 setState (CO_Data* d, e_nodeState newState);
- UNS8 setState (CO_Data d, e_nodeState newState)*函数有两个参数:
参数 *d 是指向CAN数据结构体;
参数newState 需要进入状态的枚举类型。
这段代码值得欣赏的部分有以下部分:
1. s_state_communication 结构体
2. #define StartOrStop(CommType, FuncStart, FuncStop) 宏
3.void switchCommunicationState(CO_Data* d, s_state_communication *newCommunicationState)函数
接下来详细说明以上几段代码为什么值得认真欣赏。
- s_state_communication 结构体 详细介绍
typedef struct
{
INTEGER8 csBoot_Up;
INTEGER8 csSDO;
INTEGER8 csEmergency;
INTEGER8 csSYNC;
INTEGER8 csLifeGuard;
INTEGER8 csPDO;
INTEGER8 csLSS;
} s_state_communication;
结构体种每个元素代表一种Canopen 通信对象开关功能,值1代表通信对象start功能,值0代表通信对象stop功能。
- #define StartOrStop(CommType, FuncStart, FuncStop) 宏
#define StartOrStop(CommType, FuncStart, FuncStop) \
if(newCommunicationState->CommType && d->CurrentCommunicationState.CommType == 0){\
MSG_WAR(0x9999,#FuncStart, 9999);\
d->CurrentCommunicationState.CommType = 1;\
FuncStart;\
}else if(!newCommunicationState->CommType && d->CurrentCommunicationState.CommType == 1){\
MSG_WAR(0x9999,#FuncStop, 9999);\
d->CurrentCommunicationState.CommType = 0;\
FuncStop;\
}
#define None
StartOrStop宏有三个参数,第一个参数是s_state_communication结构体元素,第二个参数为通信对象start功能,第三个参数为通信对象stop功能。
- void switchCommunicationState(CO_Data* d, s_state_communication *newCommunicationState)函数
void switchCommunicationState(CO_Data* d, s_state_communication *newCommunicationState)
{
#ifdef CO_ENABLE_LSS
StartOrStop(csLSS, startLSS(d), stopLSS(d))
#endif
StartOrStop(csSDO, None, resetSDO(d))
StartOrStop(csSYNC, startSYNC(d), stopSYNC(d))
StartOrStop(csLifeGuard, lifeGuardInit(d), lifeGuardStop(d))
StartOrStop(csEmergency, emergencyInit(d), emergencyStop(d))
StartOrStop(csPDO, PDOInit(d), PDOStop(d))
StartOrStop(csBoot_Up, None, slaveSendBootUp(d))
}
这个函数功能是根据newCommunicationState,实现通信对象的stop和start功能,函数调用了StartOrStop宏7次,代表下图中的7种通信对象:
以上部分的调用:
//声明s_state_communication结构体的变量newCommunicationState,
//并且初始{1, 0, 0, 0, 0, 0, 0},代表csBoot_Up通信对象执行start功能,剩余执行stop功能
s_state_communication newCommunicationState = {1, 0, 0, 0, 0, 0, 0};
// 调用switchCommunicationState函数,传入声明的newCommunicationState
switchCommunicationState(d, &newCommunicationState);
//switchCommunicationState函数中调用StartOrStop宏
StartOrStop(csSDO, None, resetSDO(d))
StartOrStop(csSYNC, startSYNC(d), stopSYNC(d))
StartOrStop(csLifeGuard, lifeGuardInit(d), lifeGuardStop(d))
StartOrStop(csEmergency, emergencyInit(d), emergencyStop(d))
StartOrStop(csPDO, PDOInit(d), PDOStop(d))
StartOrStop(csBoot_Up, None, slaveSendBootUp(d))
- UNS8 setState(CO_Data* d, e_nodeState newState)函数
通过传入枚举状态,然后在不同状态初始化不同的*newCommunicationState *,最终实现在不同状态控制相应的通信对象功能。
2.总结
这段代码区别QEP架构的另外一种状态机切换形式,一种可以手动编写状态机的代码架构。
后记
第一次写这种类型文章,准备不够充分,有些不正确的地方欢迎评论,如果认为有价值的地方欢迎关注、点赞、收藏三连。
关于优秀代码记录的系列,后面会一直做下去,坚持就是胜利,量变到质变。