发明内容
一、基本概念
(1)设备:设备是主控器与它可直接访问的本地硬件。主控器一般包括微控制器、微处理器、DSP、FPGA等。
(2)设备地址:设备地址是设备在整个系统中的唯一标识,只要设备在系统中,无论设备在那里,都可以通过设备地址访问它,设备地址是一个32位的无符号整数。
(3)主控设备:所谓主控设备,是一个与被控设备相对而言的设备。主控设备是处于主导地位、控制其它的设备。
(4)被控设备:被控设备是接受主控设备控制的设备。
(5)混合设备:如果一个设备即需要控制别的设备又可接受别的设备控制,它就是混合设备。
(6)端口:端口是虚拟内存,对某一个特定端口的读写可实现设备的特定功能。被控设备具有端口,主控设备不具备端口。
(7)端口地址:端口地址是端口在设备内的唯一标识,端口地址是一个32位无符号整数。
(8)端口组:端口组是具有同样属性的连续端口的组合。
二、技术特点
(1)使用ANSI C编程,将来可能增加编程语言支持。
(2)编程接口统一,无论操作设备的什么功能,都使用有限的几个函数操作。
(3)编程不区分远程设备和本地硬件,系统保留一个系统地址(符号:AW_LOCAL_ADDR,值:0x00000000)用于识别本地设备,用这个地址操作的就是本地设备。这样,设备可以使用同样的接口访问本地硬件和远程设备。
(4)多协议多网络支持:默认协议计划支持RS232、RS485、RS422、以太网、CAN、USB等网络。计划支持ModeBus、iCAN、CANOpen、DeviceNet、J1939、DMX512、MVB等协议与网络,用户还可以通过多协议接口增加特定的协议。
(5)协议及链路自动动态匹配:研发人员只需要知道设备的地址就可以编程,而不需关心主控设备与被控设备之间的网络与协议匹配问题。系统会自动选择两者均支持(并且当前网络结构支持)的协议。如果网络结构发生变化,系统会再次主动选择协议。这些过程都是透明的,研发人员无需关心。
(6)与操作系统无关:本系统使用ANSI C编程,可以移植到所有常见的嵌入式操作系统和PC操作系统中。
(7)与CPU体系结构无关:本系统使用ANSI C编程,可以移植到常见的CPU体系结构中。
(8)提供被控设备编程接口:用户可以通过这个接口实现接入特定的被控设备。
面向设备的编程把所有用网络连接起来的嵌入式系统作为一个整体来考虑,依据其在系统中的作用,把嵌入式系统分为主控设备和被控设备两类。当然,一个嵌入式系统可能即是主控设备,又是被控设备,这可以把它看做两个逻辑设备。这样,整个系统中只有主控设备和被控设备两个部分。
主控设备是整个系统的核心,用于整个系统或子系统的逻辑控制或人机接口。通常的,主控设备并不直接控制被控对象。在面向设备的编程中,“协议及链路自动动态匹配”在主控设备中实现,因此主控设备比较复杂。
被控设备是直接控制对象的,数目众多。为简化被控设备的设计和减低成本,面向设备的编程中被控设备设计成“被动应答”方式工作,没有“协议及链路自动动态匹配”过程,也不会主动联系主控设备。
主控设备通过远程调用来控制被控设备。每当主控设备调用面向设备的编程的主机接口核心函数时,对应的被控设备执行相应的函数。被控设备的函数执行完毕后,把返回值和执行结果反馈给主控设备,主控设备获得执行结果,函数返回。主控设备与被控设备的远程函数调用关系。他们每一对函数的返回值类型都是一样的,参数也十分相似,只是主控设备多一个用于指明被控设备的参数。
三、发明的效果
面向设备的编程模式是由面向API的编程方式和面向端口的编程方式集合发展而来的,具有两者的优点,避免了各自的缺点,同时极大地增强了组网能力。
这里假定需要开发控制远程蜂鸣器的嵌入式系统,其开发流程见图1。图1的左边是开发步骤,右边是对应的系统结构。
通过查看远程设备图,得知蜂鸣器的端口地址为0x1111,写1为鸣叫,写0为停止鸣叫,程序代码如程序清单1所示。
程序清单1面向设备的编程控制远程设备的蜂鸣器鸣叫
int main(void)
{
TargetInit(); /*目标板初始化*/
while(1){
awWrite(0x05,0x1111,0); /*BEEP停止蜂鸣*/
awWrite(AW_LOCAL_ADDR,0x00001000,50); /*等待时间间隔*/
awWrite(0x05,0x1111,1); /*BEEP蜂鸣*/
awWrite(AW_LOCAL_ADDR,0x00001000,50); /*等待时间间隔*/
}
return 0;
}
可以看出,这种编程模式非常简单。面向设备编程模式与传统编程模式的对比见表3。
具体实施方式
一、总体设计思想
面向设备的编程把所有用网络连接起来的嵌入式系统作为一个整体来考虑,依据其在系统中的作用,把嵌入式系统分为主控设备和被控设备两类。当然,一个嵌入式系统可能即是主控设备,又是被控设备,这可以把它看做两个逻辑设备。这样,整个系统中只有主控设备和被控设备两个部分。
主控设备是整个系统的核心,用于整个系统或子系统的逻辑控制或人机接口。通常的,主控设备并不直接控制被控对象。在面向设备的编程中,“协议及链路自动动态匹配”在主控设备中实现,因此主控设备比较复杂。
被控设备是直接控制对象的,数目众多。为简化被控设备的设计和减低成本,面向设备的编程中被控设备设计成“被动应答”方式工作,没有“协议及链路自动动态匹配”过程,也不会主动联系主控设备。
主控设备通过远程调用来控制被控设备。每当主控设备调用面向设备的编程的主机接口核心函数时,对应的被控设备执行相应的函数。被控设备的函数执行完毕后,把返回值和执行结果反馈给主控设备,主控设备获得执行结果,函数返回。主控设备与被控设备的远程函数调用关系见表4。他们每一对函数的返回值类型都是一样的,参数也十分相似,只是主控设备多一个用于指明被控设备的参数。
二、基本框图
面向设备的编程的基本框图见图3。由图3可知,面向设备的编程主控设备由应用程序、设备搜索引擎、协议驱动管理、协议驱动和物理链路组成。而被控设备由初始化程序、设备端口管理层、端口组管理层、协议驱动、硬件驱动、物理硬件和物理链路组成。
三、一般处理流程
图4-图7为主控设备访问远程设备中函数awRead()的一般处理流程,主控设备首先查找ARP表,如果ARP表中存有被控设备信息,调用被控设备函数开始执行,如图4所示。如果ARP表中不存在此远程设备的情况,则需要请求添加此设备,如图5所示。在远程设备添加成功后,调用被控设备函数开始执行,如图6所示。为程序处理完的应答返回,如图7所示。函数awWrite()、函数awReadEx()和函数awWriteEx()的处理流程与函数awRead()相似,这里不再给出。
四、用户编程接口
(1)端口属性
每一个端口都有自己的属性。目前,端口由两个子属性组成,分别是读写属性和端口位宽,下面分别介绍。
1.读写属性
端口的读写属性用于控制端口的读写模式,目前具有两种模式:
a.IO模式
IO模式用符号AW_IO_MODE表示。对IO模式端口进行多数据读写操作时,端口地址不会增加。操作的是同一个端口。
b.内存模式
内存模式用符号AW_MEMARY_MODE表示。对内存模式端口进行多数据读写操作时,端口地址会增加。操作的是连续端口。
2.位宽属性
端口的位宽属性用于控制端口的位宽,目前具有四种模式:分别为字节宽度(8位)、双字节宽度(16位)、字宽度(32位)和位宽度(1位)。他们分别使用符号AW_BYTE_NODE、AW_DBYTE_NODE、AW_WORD_NODE、AW_BIT_NODE表示。
(2)返回值
除了函数awAddressGet()和函数awTick()外,面向设备的编程的其它函数均遵守同样的返回值规则:
1.返回值为32位有符号整数;
2.返回值为负数时,函数执行不正确;
3.返回值大于等于0为函数执行正确。一般情况,返回值大于0时,表示完成的数据个数。
函数返回值的详细情况见表5。随着面向设备的编程的升级,返回值的数目可能会增加。
(3)主控设备编程接口(核心编程接口)
这是一般用户使用的接口,也是最常用的API。这部分有4个函数,分别是awRead()、awWrite()、awReadEx()和awWriteEx()。其中函数awRead()和awWrite()是对指定设备的指定端口用默认的方式读写,读写的数据都会转化为32位无符号数。而awReadEx()和awWriteEx()用于一次对端口读写多个数据,需要指定读写模式,这个模式还必须与端口的模式一致。
这几个函数的详细介绍请参考表6至表9。
(4)被控设备编程接口
在设计一个控制系统时,被控设备一般会选择标准设备,不需要用户编程。如果使用非标准的被控设备,就需要进行产品研发。从图1.6可以看出,被控设备的应用程序仅仅是初始化而已。如果用户选择本公司(广州致远电子有限公司)的半成品模块,大多数情况也无需开发,只需要通过向导(PC机程序)配置需要的功能就可以生成需要的代码。如果这些半成品模块不能完全满足系统需求,就要进行研发。
被控设备要给主控设备提供端口,所以被控设备接口主要是端口的管理。面向设备的编程被控设备的任何一个端口都从属于某个端口组,这样对端口的管理就转换成对端口组的管理。被控设备的研发就是设计这些端口组。AnyWhere中定义了端口组信息结构体,其定义见程序清单2。
程序清单2 端口组信息
struct awDeviceInfo{
INT32U ulAddrStart; /*开始端口地址*/
INT32U ulLen; /*端口组地址长度*/
INT8U ucWidth; /*端口宽度*/
INT8U ucProp; /*端口属性*/
INT8U ucRemoteEn; /*远程访问许可*/
INT8U ucMaxDelay; /*最长延时*/
/*构造函数*/
INT32S (*pfuncCreate)(const struct awDeviceInfo*pdiThis);
/*析构函数*/
INT32S (*pfuncDel)(const struct awDeviceInfo*pdiThis);
/*读端口数据*/
INT32S (*pfuncRead)(const struct awDeviceInfo*pdiThis,
INT32U usOffSet,
INT16U usLen,
void *pvData);
/*写端口数据*/
INT32S (*pfuncWrite)(const struct awDeviceInfo*pdiThis,
INT32U usOffSet,
INT16U usLen,
void *pvData);
};
typedef struct awDeviceInfo AW_DEVICE_INFO;
从程序清单2可以看出,端口组信息结构体具有10个成员,分别是ulAddrStart、ulLen、ucWidth、ucProp、ucRemoteEn、ucMaxDelay、pfuncCreate、pfuncDel、pfuncRead和pfuncWrite,下面分别介绍:
ulAddrStart:端口组的开始地址,大于等于这个端口地址且小于(ulAddrStart+ulLen)的端口地址属于这个端口组;
ulLen:端口组地址的长度,也就是端口组端口的个数;
ucWidth:端口的宽度,可选值为AW_BYTE_NODE、AW_DBYTE_NODE、AW_WORD_NODE和AW_BIT_NODE(参考7.4(1)小节);
ucProp:端口属性,可选值为AW_IO_MODE和AW_MEMARY_MODE(参考7.4(1)小节);
ucRemoteEn:远程访问许可,为TRUE时允许远程主控设备访问这个端口组,否则只允许本地设备通过地址AW_LOCAL_ADDR(0)访问;
ucMaxDelay:最长延时,用于指示本端口组读写一个端口的最长延时;
pfuncCreate:函数指针,端口组被创建时被调用,其唯一的参数为指向自己端口组信息的结构体变量的指针。其返回值参考7.4(2)节;
pfuncDel:函数指针,端口组删除时被调用,其唯一的参数为指向自己端口组信息结构体变量的指针。其返回值参考7.4(2)节;
pfuncRead:函数指针,读端口组某个或多个端口时被调用,其参数和返回值参考表10;
(5)初始化接口
面向设备的编程需要初始化后才能使用。初始化代码大多数通过向导(PC机程序)生成,一般不需要用户关心。
面向设备的编程的初始化接口有6个函数,分别为awInit()、awAddressSet()、awAddressGet()、awRemoteDrivesAdd()、awRemoteDrivesDel()和awTick()。其中函数awInit()是必须调用的,调用了这个函数后,调用其它函数才会有效,其它函数可以在调用函数awInit()后任何时间调用。一般情况,函数awAddressSet()、awRemoteDrivesAdd()会在初始化时调用,而函数awAddressGet()和awRemoteDrivesDel()是与它们相对应的函数。至于函数awTick(),它是需要周期性调用的函数,正常使用看不见它。这些函数请参考表14至表19。
五、主控设备通讯协议驱动编程接口
(1)驱动程序信息
由表1.17可知,驱动信息由一个结构体AW_DRIVER_INFO_BASE保存,它定义见程序清单3。
程序清单3 网络协议驱动信息
struct awDriverInfoBase{
/*构造函数*/
INT32S (*pfuncCreate)(struct awDriverInfoBase*pdiThis);
/*析构函数*/
INT32S (*pfuncDel)(struct awDriverInfoBase*pdiThis);
/*查看指定设备否否在本网络中*/
INT32S (*pfuncArp)(struct awDriverInfoBase*pdiThis,INT32U ulDevice);
/*读端口数据*/
INT32S (*pfuncRead)(struct awDriverInfoBase*pdiThis,
INT32U ulDevice,
INT32U ulAddr,
INT16U usLen,
void *pvData,
INT8U ucMod);
/*写端口数据*/
INT32S (*pfuncWrite)(struct awDriverInfoBase*pdiThis,
INT32U ulDevice,
INT32U ulAddr,
INT16U usLen,
void *pvData,
INT8U ucMod);
INT16U usTtls; /*在ARP表中保留的时间*/
INT16U usMtu; /*网络帧的有效数据大小*/
};
typedef struct aw_driver_info_base AW_DRIVER_INFO_BASE;
从程序清单2.1可以看出,协议驱动信息结构体具有7个成员,分别是pfuncCreate、pfuncDel、pfuncArp、pfuncRead、pfuncWrite、usTtls和usMtu,下面分别介绍:
pfuncCreate:函数指针,增加协议驱动程序时被调用,其唯一的参数为指向自己的驱动信息结构体变量的指针。其返回值参考表1.5;
pfuncDel:函数指针,删除协议驱动程序时被调用,其唯一的参数为指向自己的驱动信息结构体变量的指针。其返回值参考表1.5;
pfuncArp:函数指针,用于在本驱动所管理的网络中搜索指定的设备,其参数和返回值参考表20;
pfuncRead:函数指针,用于读本驱动支持的网络上的被控设置,其参数和返回值参考表21;
pfuncWrite:函数指针,用于写本驱动支持的网络上的被控设置,其参数和返回值参考表22;
usTtls:用于表示本驱动在ARP表(设备与驱动对照表)中的生存时间,以调用函数awTick()的周期为单位;
usMtu:一个网络帧可以负荷的有效数据数(以字节为单位),不包括设备号、端口号、协议添加数据等。
pfuncWrite:函数指针,写端口组某个或多个端口时被调用,其参数和返回值参考表11。用户定义好端口组后,就可以调用函数awDeviceAdd()增加一个端口组,然后这些端口就可以使用了。
与函数awDeviceAdd()对应,面向设备的编程还有一个函数awDeviceDel(),它为删除一个端口组,其详细说明见表13。
(2)构造驱动自身的信息结构体
驱动程序信息结构体仅保存基本的面向设备的编程需要的驱动程序信息,没有预留保存驱动程序自身信息的成员。如果驱动程序需要添加自己的成员,可以参照程序清单4所示的方法定义自身的信息结构体,只需要在调用函数awRemoteDrivesAdd()(参考表1.17)和函数awRemoteDrivesDel()(参考表1.18)时进行一下强制指针转换即可。当然,驱动程序使用pdiThis指针时(参考表20、表21和表22)需要反向转换。
程序清单4 构造驱动自身的信息结构体
struct xxxx_driver_info{
AW_DRIVER_INFO_BASE dibInfo; /*面向设备的编程需要的驱动信息,必
须是第一个成员 */
/*以下添加驱动程序自己的成员*/
};
typedef xxxx_driver_info XXXX_DRIVER_INFO;
(3)设备地址映射
由表1.17可知,驱动信息由一个结构体AW_DRIVER_INFO_BASE保存,它定义见程序清单3。
(4)设备地址映射
用户通讯协议的设备地址范围和表示方式与面向设备的编程不一定相同。如果不相同,需要把用户通讯协议的设备地址映射到面向设备的编程地址。为了不与其它协议的地址冲突,一般需要初始化或动态指定映射关系,而不是在代码中写死。这个映射关系可以在驱动自身的信息结构体中添加成员指定。
(5)使用ARP表简化动态地址映射
ARP表中有一个8字节的成员保留给驱动程序,驱动程序可以任意使用。一般来说,当驱动程序无法实现静态的地址映射关系时(如TCP/IP协议),可以用它来保存动态的地址映射关系。面向设备的编程使用两个函数用于设置和获得ARP表的这个成员,分别是函数awArpRsvdSet()和函数awArpRsvdGet()。函数awArpRsvdSet()用于设置设备地址与Rsvd(协议的物理地址)的对应关系,而函数awArpRsvdGet()用于获得这个对应关系。这两个函数请参考表23和表24。
六、被控设备通讯协议驱动编程接口
(1)概述
被控设备设计成被动应答方式,数据帧从哪里来就将反馈的数据帧发送回哪里去。对于主控设备的协议驱动来说,有三个发送函数pfuncArp()、pfuncRead()和pfuncWrite(),因此,被控设备的协议驱动主要是处理三种数据帧。
(2)驱动程序信息结构体
因为没有驱动管理器,正常来说,被控设备不需要驱动程序信息结构体,不过为了兼容主控设备,被控设备的驱动程序信息结构体与主控设备的驱动程序信息结构体一样。由于没有驱动管理器,成员pfuncArp、pfuncRead和pfuncWrite均为NULL,成员usTtls和usMtu也可以任意赋值。其它部分与主控设备一致。
(3)端口地址转换
协议的设备拱功能分配的方式与面向设备的编程可能不同,如ModBus分为线圈地址和寄存器地址,需要把他们映射到面向设备的编程地址,者可以在驱动程序信息就结构体中增加成员来实现。
(4)ARP帧
被控设备接收到ARP帧后,需要把ARP帧中的设备地址信息提取出来,然后与本机设备地址比较,如果相同,则回复主控设备,说明自己就是目标设备。获得本机设备地址可以通过调用函数awAddressGet()获得(参考表16)。当然,如果协议的地址空间与面向设备的编程地址空间不一致,则需要地址转换后才比较。
(5)read帧
接收到read帧,主控设备可能调用了函数awRead(),也可能调用了函数awReadEx()(参考表21)。如果主控设备调用了函数awRead(),被控设备需要调用函数awDeviceRead()。如果主控设备调用了函数awReadEx(),被控设备需要调用函数awDeviceReadEx()。函数awDeviceRead()的具体说明见表25;函数awDeviceReadEx()的具体说明见表26。
当函数awDeviceRead()或函数awDeviceReadEx()返回后,驱动需要把读到的数据和返回值通过同样的网络接口发送给主控设备。
值得注意的是,接收到read帧也需要判断一下设备地址是否是自身地址。
(6)write帧
接收到write帧,主控设备可能调用了函数awwrite(),也可能调用了函数awWriteEx()(参考表9)。如果主控设备调用了函数awwrite(),被控设备需要调用函数awDeviceWrite()。如果主控设备调用了函数awWriteEx(),被控设备需要调用函数awDeviceWriteEx()。函数awDeviceWrite()的具体说明见表27;函数awDeviceWriteEx()的具体说明见表28。
当函数awDeviceRead()或函数awDeviceReadEx()返回后,驱动需要把读到的数据和返回值通过同样的网络接口发送给主控设备。
值得注意的是,接收到read帧也需要判断一下设备地址是否是本身。
七、默认通讯协议
(1)默认通讯协议通讯流程
面向设备的编程默认协议使用锁步方式工作,即主控设备发送一个命令帧,然后等待被控设备一个回复帧。如果在规定的时间内主控设备没有得到正确的回复帧,则进行错误处理。否则准备下一次通讯过程。
(2)默认协议帧结构程
默认协议所有数据以大端方式存储。本小节所述的帧格式中,第0字节为第一个发送的字节,第1字节为第二个发送的字节,以此类推。
1.帧结构
面向设备的编程默认协议帧由3部分组成:帧头、帧数据和帧校验。其中帧头最先发送,帧校验最后发送,示意如下:
0~15 |
16~(n-3) |
(n-2)~(n-1) |
帧头 |
数据,格式有帧类型决定 |
帧校验 |
2.帧头
面向设备的编程默认协议帧头为16个字节,具体含义如下:
假设_GucBuf为帧的起始位置,usLen为保存帧的长度的变量,则从帧头获得帧长度的代码如下:
usLen=((AW_ZY_PACKET_HARD*)_GucBuf)->usPacketLen;
ntohs(usLen);
假设_GucBuf为帧的起始位置,ulDevice为保存帧的源地址的变量,则从帧头获得源地址的代码如下:
ulDevice=((AW_ZY_PACKET_HARD*)_GucBuf)->ulSrcAddr;
ntohl(ulDevice);
3.校验
面向设备的编程默认协议帧采取16位crc校验,校验数据放在帧的最后。Crc校验的表达式为x^16+x^12+x^5+x^0。
4.ARP帧
ARP帧的帧代码为0x00,仅包含帧头和帧校验部分。
5.ARP回复帧
ARP回复帧的帧代码为0x80,仅包含帧头和帧校验部分。
6.Read帧
Read帧的帧代码为0x01,长度为24,帧结构如下:
0~15 |
16~19 |
20~21 |
22~23 |
帧头 |
端口地址 |
读取数据个数 |
帧校验 |
7.Read回复帧
Read帧的帧代码为0x81,长度不定,帧结构如下:
0~15 |
16~19 |
20~(n-3) |
(n-2)~(n-1) |
帧头 |
返回值 |
读到的数据 |
帧校验 |
8.Write帧
Write帧的帧代码为0x02,长度不定,帧结构如下:
0~15 |
16~19 |
20~21 |
22~(n-3) |
(n-2)~(n-1) |
帧头 |
端口地址 |
写入数据个数 |
要写入的数据 |
帧校验 |
8.Write回复帧
Write帧的帧代码为0x82,长度为22字节,帧结构如下:
0~15 |
16~19 |
20~21 |
帧头 |
返回值 |
帧校验 |
(3)底层驱动信息结构体
面向设备的编程默认协议底层驱动具有自己的驱动信息结构体,其定义见程序清单5。
程序清单5 默认协议底层驱动信息
/***************************************************************************
默认协议驱动信息
***************************************************************************/
struct awZyDriverInfo{
AW_DRIVER_INFO_BASE dibInfo; /*驱动信息*/
/*构造函数*/
INT32S (*pfuncCreate)(struct awZyDriverInfo*pdiThis);
/*析构函数*/
INT32S (*pfuncDel)(struct awZyDriverInfo*pdiThis);
/*发送数据包*/
INT32S (*pfuncSend)(struct awZyDriverInfo*pdiThis,
INT32U ulDevice,
INT8U *pucData1,
INT16U usLen1,
INT8U *pucData2,
INT16U usLen2,
INT8U ucCrc[2]);
unsigned int uiReviceDelay; /*等待接收延迟时间*/
};
typedef struct awZyDriverInfo AW_ZY_DRIVER_INFO;
从程序清单5可以看出,协议驱动信息结构体具有5个成员,分别是dibInfo、pfuncCreate、pfuncDel、pfuncSend、uiReviceDelay,下面分别介绍:
dibInfo:结构体变量,面向设备的编程驱动信息结构体变量,用于驱动程序管理;
pfuncCreate:函数指针,增加默认协议驱动程序时被调用,其唯一的参数为指向自己的驱动信息结构体变量的指针;
pfuncDel:函数指针,删除默认协议驱动程序时被调用,其唯一的参数为指向自己的驱动信息结构体变量的指针;
pfuncSend::函数指针,用于发送一个数据帧,参考表29;
uiReviceDelay:变量,用于指示从发送数据帧到接收回复数据帧的最大间隔。
(4)发送数据帧
发送数据帧的示意代码见程序清单6。
程序清单6 发送数据帧示意代码
INT32S pfuncSend(AW_ZY_DRIVER_INFO*pdiThis,
INT32U ulDevice,
INT8U *pucData1,
INT16U usLen1,
INT8U *pucData2,
INT16U usLen2,
INT8U ucCrc[2])
{
INT8U *pucData; /*发送数据缓冲区*/
INT16U usLen; /*发送数据长度*/
if(pdiThis==NULL){
return-AW_PARAMETER_ERR;
}
if(pucData1==NULL){
return-AW_PARAMETER_ERR;
}
if(usLen1==0){
return-AW_PARAMETER_ERR;
}
if(pucData2==NULL&&usLen2!=0){
return-AW_PARAMETER_ERR;
}
if(pucData2!=NULL&&usLen2==0){
return-AW_PARAMETER_ERR;
}
/*传输数据*/
if(ulDevice==AW_BROADCAST_ADDR){ /*广播地址*/
目标地址=广播地址;
}else{
目标地址=地址映射表[ulDevice];
}
usLen =(INT16U)(usLen1+usLen2+sizeof(INT8U)*2);
pucData=(INT8U*)awHeapMalloc(usLen);
if(pucData==NULL){
return-AW_NO_MEMARY;
}
memcpy(pucData,pucData1,usLen1);
memcpy(pucData+usLen1,pucData2,usLen2);
memcpy(pucData+usLen1+usLen2,ucCrc,sizeof(INT8U)*2);
send(目标地址,pucData,usLen);/*向“目标地址“发送长度为usLen的数据帧*/
awHeapFree(pucData);
if(pucData2!=NULL){
awHeapFree(pucData2); /*发送成功释放空间*/
}
return AW_OK;
}
(5)接收数据帧
一般用线程来处理接收到的数据帧,这个线程的示意代码见程序清单7。
程序清单7 接收数据帧处理
static void_awXXXZyThread(void*pvData)
{
AW_ZY_DRIVER_INFO *pdiInfo;
INT8U *pucBuf;
INT16U usTmp1;
pdiInfo=(AW_ZY_DRIVER_INFO*)pvData;
while(1){
pucBuf=awHeapMalloc(数据帧长度);
if(pucBuf==NULL){
awThreadDelay(1);
continue;
}
if(recv(pucBuf)==成功){ /*接收数据帧成功*/
if(awZyPacketCheck(pdiInfo,(AW_ZY_PACKET_HARD*)pucBuf)==
AW_OK){
awZyRevice(pdiInfo,(AW_ZY_PACKET_HARD*)pucBuf);
}else{
awHeapFree(pucBuf);
}
}
}
}
八、具体应用
面向设备的编程技术与方法已经在广州致远电子有限公司的EPC-266x和EPC-296x工控主板上应用,并逐步有序的推广到公司大部分嵌入式产品中。
EPC-266x和EPC-296x是广州致远电子有限公司开发的基于32位ARM处理器LPC2460的可扩展的嵌入式工控主板,产品兼容AnyWhere软件平台,机械结构尺寸遵循PC/104相关规范,提供MiniISA总线接口。EPC-266x具有资源丰富、内存容量大、接口齐全、功耗低、可靠性高等特点。预装正版μC/OS-II操作系统,并内置TCP/IP协议、iCAN协议(EPC-296x)、USB2.0 Host协议、FAT32文件管理系统等,EPC-266x可实现远程在线固件升级。
EPC-266x工控主板和EPC-296x工控主板可在-40℃~+85℃宽温度范围内稳定工作,满足工业级产品的各种应用要求。
EPC-266x工控主板和EPC-296x工控主板主要应用于工业控制自动化、煤炭或石油产品税控系统、小区门禁安防管理、大型车库管理系统、交通控制系统、环境数据记录、通信协议转换器等领域中。
表1传统编程模式对照表
研发方式 |
基于寄存器 |
基于API |
基于端口 |
研发周期 |
长 |
较长 |
较短 |
代码量 |
大 |
较小 |
较小 |
维护工作 |
复杂 |
比较简单 |
简单 |
移植 |
难度大 |
比较方便 |
仅限于PLC |
研发难度 |
高 |
较高 |
低 |
功能与灵活性 |
高 |
较高 |
低 |
对研发人员的要求 |
高 |
较高 |
低 |
表2传统编程模式开发联网控制系统对照表
研发方式 |
基于寄存器 |
基于API |
基于端口 |
研发周期 |
很长 |
长 |
较短 |
代码量 |
巨大 |
大 |
小 |
维护工作 |
很复杂 |
复杂 |
简单 |
移植 |
难度大 |
比较方便 |
仅限于PLC |
研发难度 |
很高 |
高 |
低 |
功能与灵活性 |
高 |
较高 |
低 |
对研发人员的要求 |
很高 |
高 |
低 |
表3面向设备的编程与传统编程模式的对照表
研发方式 |
基于寄存器 |
基于API |
基于端口 |
研发周期 |
很长 |
长 |
较短 |
代码量 |
巨大 |
大 |
小 |
维护工作 |
很复杂 |
复杂 |
简单 |
移植 |
难度大 |
比较方便 |
仅限于PLC |
研发难度 |
很高 |
高 |
低 |
功能与灵活性 |
高 |
较高 |
低 |
对研发人员的要求 |
很高 |
高 |
低 |
表4设备远程调用关系
功能 |
主控设备函数 |
被控设备函数 |
端口读 |
awRead(参考表) |
awDeviceRead |
功能 |
主控设备函数 |
被控设备函数 |
端口写 |
awWrite(参考表) |
awDeviceWrite |
扩展端口读 |
awReadEx(参考表) |
awDeviceReadEx |
扩展端口写 |
awWriteEx(参考表) |
awDeviceWriteEx |
表5面向设备的编程的返回值
符号 |
值 |
含义 |
备注 |
AW_OK |
0 |
操作成功 |
|
-AW_NOT_OK |
-1 |
操作失败 |
|
-AW_PARAMETER_ERR |
-2 |
参数错误 |
|
-AW_NO_FIND_DEVICE |
-3 |
没有发现设备 |
|
-AW_NODE_FULL |
-4 |
节点已满 |
节点用于保存端口组信息,节点已满就不能增加端口组 |
-AW_NO_FIND_NODE |
-5 |
没有发现节点 |
没有对应的端口 |
-AW_NO_FIND_FUNCTION |
-6 |
没有发现指定函数 |
写端口返回此值,指定端口只读读端口返回此值,指定端口只写 |
-AW_NO_PORT |
-7 |
不存在的端口 |
|
-AW_NO_THIS_ADDRES |
-8 |
地址不存在 |
指的是端口地址 |
-AW_DRIVER_FULL |
-9 |
网络驱动满 |
增加网络驱动时才可能返回此值 |
-AW_NO_FIND_DRIVER |
-10 |
没有发现网络驱动 |
|
-AW_ARP_FULL |
-11 |
ARP表满 |
内部使用 |
符号 |
值 |
含义 |
备注 |
-AW_NO_MEMARY |
-12 |
内存不足 |
|
-AW_TIME_OUT |
-13 |
超时 |
|
-AW_PACKET_ERR |
-14 |
帧错误 |
内部使用 |
表6awRead
函数名称 |
awRead |
函数原型 |
INT32S awRead(INT32U ulDevice,INT32U ulAddr,INT32U*pulData) |
功能描述 |
端口读 |
输入参数 |
ulDevice:设备地址ulAddr:端口地址 |
输出参数 |
pulData:读到的数据 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表7awWrite
函数名称 |
awWrite |
函数原型 |
INT32S awWrite(INT32U ulDevice,INT32U ulAddr,INT32UulData) |
功能描述 |
端口写 |
函数名称 |
awWrite |
输入参数 |
ulDevice:设备地址ulAddr:端口地址ulData:写入的数据 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表8awReadEx
函数名称 |
awReadEx |
函数原型 |
INT32S awReadEx(INT32U ulDevice,INT32U ulAddr,INT16U usLen,void *pvData,INT8U ucMod) |
功能描述 |
扩展端口读 |
输入参数 |
ulDevice:设备地址ulAddr:端口地址usLen:数据数目ucMod:读写模式与端口位宽的或 |
输出参数 |
pvData:读到的数据 |
返回值 |
>=0:完成的数据数目负数:错误,参考表5 |
特殊说明 |
ucMod必须与端口的实际属性一致 |
表9awWriteEx
函数名称 |
awWriteEx |
函数原型 |
INT32S awWriteEx(INT32U ulDevice,INT32U ulAddr,INT16U usLen,void *pvData,INT8U ucMod) |
功能描述 |
扩展端口写 |
输入参数 |
ulDevice:设备地址ulAddr:端口地址usLen:数据数目ulData:数据ucMod:读写模式与端口位宽的或 |
输出参数 |
无 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
特殊说明 |
ucMod必须与端口的实际属性一致 |
表10pfuncRead
函数名称 |
pfuncRead |
函数原型 |
INT32S pfuncRead(const AW_DEVICE_INFO*pdiThis,INT32U usOffSet,INT16U usLen,void *pvData); |
功能描述 |
读端口组内端口时调用的函数,完成实际的读操作 |
输入参数 |
pdiThis:端口组属性usOffSet:端口偏移,为端口地址-ulAddrStartusLen:以端口宽度为单位的读写长度 |
输出参数 |
pvData::读到的数据 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
表11pfuncWrite
函数名称 |
pfuncWrite |
函数原型 |
INT32S pfuncWrite(const AW_DEVICE_INFO*pdiThis,INT32U usOffSet,INT16U usLen,void *pvData) |
功能描述 |
写端口组内端口时调用的函数,完成实际的写操作 |
输入参数 |
pdiThis:端口组属性usOffSet:端口偏移,为端口地址-ulAddrStartusLen:以端口宽度为单位的读写长度pvData::写入的数据 |
输出参数 |
无 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
特殊说明 |
无 |
表12awDeviceAdd
函数名称 |
awDeviceAdd |
函数原型 |
INT32S awDeviceAdd(const AW_DEVICE_INFO*padiinfo) |
功能描述 |
增加端口组 |
输入参数 |
padiinfo:端口组属性 |
函数名称 |
awDeviceAdd |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表13awDeviceDel
函数名称 |
awDeviceDel |
函数原型 |
INT32S awDeviceDel(const AW_DEVICE_INFO*padiinfo) |
功能描述 |
删除端口组 |
输入参数 |
padiinfo:端口组属性 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表14awInit
函数名称 |
awInit |
函数原型 |
INT32S awInit(void) |
功能描述 |
面向设备的编程初始化 |
输入参数 |
无 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
必须调用此函数后才能使用面向设备的编程 |
表15awAddressSet
函数名称 |
awAddressSet |
函数原型 |
INT32S awAddressSet(INT32U ulAddr) |
功能描述 |
设置anywere地址 |
输入参数 |
ulAddr:anywere地址 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
必须调用此函数后才能使用远程设备,或接受远程连接 |
表16awAddressGet
函数名称 |
awAddressGet |
函数原型 |
INT32U awAddressGet(void) |
功能描述 |
获得anywere地址 |
输入参数 |
无 |
输出参数 |
无 |
返回值 |
anywere地址 |
特殊说明 |
无 |
表17awRemoteDrivesAdd
函数名称 |
awRemoteDrivesAdd |
函数原型 |
INT32S awRemoteDrivesAdd(AW_DRIVER_INFO_BASE*pdibDriver) |
函数名称 |
awRemoteDrivesAdd |
功能描述 |
增加网络驱动 |
输入参数 |
无 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
有网络驱动后再能进行远程连接 |
表18awRemoteDrivesDel
函数名称 |
awRemoteDrivesDel |
函数原型 |
INT32S awRemoteDrivesDel(AW_DRIVER_INFO_BASE*pdibDriver) |
功能描述 |
删除网络驱动 |
输入参数 |
pdibDriver:网络驱动 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表19awTick
函数名称 |
awTick |
函数原型 |
void awTick(void) |
功能描述 |
时钟节拍处理,需要定时调用 |
输入参数 |
None |
函数名称 |
awTick |
输出参数 |
无 |
返回值 |
None |
特殊说明 |
主控设备才需要 |
表20pfuncArp
函数名称 |
pfuncArp |
函数原型 |
INT32S pfuncArp(AW_DRIVER_INFO_BASE*pdiThis,INT32U ulDevice) |
功能描述 |
查看指定设备是否在本网络中 |
输入参数 |
pdiThis:本驱动信息ulDevice:要查找的设备地址 |
输出参数 |
无 |
返回值 |
AW_OK:存在 负数:错误,参考表5 |
特殊说明 |
无 |
表21pfuncRead
函数名称 |
pfuncRead |
函数原型 |
INT32S pfuncRead(AW_DRIVER_INFO_BASE *pdiThis,INT32U ulDevice,INT32U ulAddr,INT16U usLen,void *pvData,INT8U ucMod) |
函数名称 |
pfuncRead |
功能描述 |
读指定端口数据当usLen==1&&ucMod==0,ucMod为端口默认值,pvData指向4字节整数(注:4字节整数取决于本地大小端模式)。其它情况usLen和ucMod必须为有效值 |
输入参数 |
pdiThis:本驱动属性ulDevice:设备地址ulAddr:端口地址usLen:以字节为单位的数据长度,0为端口默认长度ucMod:读写模式,参考7.4(1)小节 |
输出参数 |
pvData::读到的数据 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
特殊说明 |
无 |
表22pfuncWrite
函数名称 |
pfuncWrite |
函数原型 |
INT32S pfuncWrite(AW_DRIVER_INFO_BASE*pdiThis,INT32U ulDevice,INT32U ulAddr,INT16U usLen,void *pvData,INT8U ucMod) |
功能描述 |
读指定端口数据当usLen==1&&ucMod==0,ucMod为端口默认值,pvData指向4字节整数(注:4字节整数取决于本地大小端模式)。其它情况usLen和ucMod必须为有效值 |
输入参数 |
pdiThis:本驱动属性ulDevice:设备地址ulAddr:端口地址pvData:数据缓冲区usLen:以字节为单位的数据长度,0为端口默认长度ucMod:读写模式,参考7.4(1)小节 |
函数名称 |
pfuncWrite |
输出参数 |
无 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
特殊说明 |
无 |
表23awArpRsvdSet
函数名称 |
awArpRsvdSet |
函数原型 |
INT32S awArpRsvdSet(INT32U ulDevice,INT8U ucRsvd[8]) |
功能描述 |
设置Arp表指定表项的Rsvd值 |
输入参数 |
ulDevice:设备地址ucRsvd:Rsvd值 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表24awArpRsvdGet
函数名称 |
awArpRsvdGet |
函数原型 |
INT32S awArpRsvdGet(INT32U ulDevice,INT8U ucRsvd[8]) |
功能描述 |
被控设备端口写 |
输入参数 |
ulDevice:设备地址 |
输出参数 |
ucRsvd:Rsvd值 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
表25awDeviceRead
函数名称 |
awDeviceRead |
函数原型 |
INT32S awDeviceRead(INT32U*pulData) |
功能描述 |
被控设备端口读 |
输入参数 |
ulAddr:端口地址 |
输出参数 |
pulData:读到的数据 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表26awDeviceReadEx
函数名称 |
awReadEx |
函数原型 |
INT32S awDeviceReadEx(INT32U ulAddr,INT16U usLen,void *pvData,INT8U ucMod) |
功能描述 |
被控设备扩展端口读 |
输入参数 |
ulAddr:端口地址usLen:数据数目ucMod:读写模式与端口位宽的或 |
输出参数 |
pvData:读到的数据 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
特殊说明 |
ucMod必须与端口的实际属性一致 |
表27awDeviceWrite
函数名称 |
awDeviceWrite |
函数原型 |
INT32S awDeviceWrite(INT32U ulAddr,INT32U ulData) |
功能描述 |
被控设备端口写 |
输入参数 |
ulAddr:端口地址ulData:写入的数据 |
输出参数 |
无 |
返回值 |
AW_OK:成功 负数:错误,参考表5 |
特殊说明 |
无 |
表28awDeviceWriteEx
函数名称 |
awDeviceWriteEx |
函数原型 |
INT32S awDeviceWriteEx(INT32U ulAddr,INT16U usLen,void *pvData,INT8U ucMod) |
功能描述 |
被控设备扩展端口写 |
输入参数 |
ulAddr:端口地址usLen:数据数目ulData:数据ucMod:读写模式与端口位宽的或 |
输出参数 |
无 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
函数名称 |
awDeviceWriteEx |
特殊说明 |
ucMod必须与端口的实际属性一致 |
表29pfuncSend
函数名称 |
pfuncSend |
函数原型 |
INT32S pfuncSend(AW_ZY_DRIVER_INFO*pdiThis,INT32U ulDevice,INT8U *pucData1,INT16U usLen1,INT8U *pucData2,INT16U usLen2,INT8U ucCrc[2]) |
功能描述 |
默认协议底层驱动发送数据帧 |
输入参数 |
pdiThis:默认协议底层驱动信息ulDevice:设备地址pucData1:第一段数据usLen1:以字节为单位的第一段数据长度pucData2:第二段数据usLen2:以字节为单位的第二段数据长度ucCrc:crc校验 |
输出参数 |
无 |
返回值 |
>=0:完成的数据数目 负数:错误,参考表5 |
特殊说明 |
|