(通信 电子)ZigBee学习资料
更新时间:2024-05-27 15:01:01 阅读量: 综合文库 文档下载
- 通信行程卡推荐度:
- 相关推荐
一、概述
OSAL (Operating System Abstraction Layer)翻译为“操作系统抽象层”。如何理解这个复杂的名词呢表面上看它是作为操作系统存在的可是为什么又加上“抽象层”呢?它的本质是什么?在Z-Stack协议栈中它又扮演了什么角色呢?要解答这些问题我们必须先从宏观入手渐渐深入探究,最后答案自然会浮出水面。
从这幅图中我们可以很清楚地从宏观上了解ZigBee协议的结构。可是经过粗略的浏览我们并没有发现任何OSAL的踪迹。当然我们都知道Z-Stack与ZigBee之间并不能完全划等号。Z-Stack是ZigBee的具体实现所以存在于Z-Stack中的OSAL并不一定出现在ZigBee中。但是我们可以在ZigBee中找到些许OSAL的踪影。
在ZigBee协议中协议本身已经定义了大部分内容。在基于ZigBee协议的应用开发中用户只需要实现应用程序框架即可。从上图可以看出应用程序框架中包含了最多240个应用程序对象。如果我们把一个应用程序对象看做为一个任务的话那么应用程序框架将包含一个支持多任务的资源分配机制。于是OSAL便有了存在的必要性它正是Z-Stack为了实现这样一个机制而存在的。
OSAL就是以实现多任务为核心的系统资源管理机制。所以OSAL与标准的操作系统还是有很大的区别的。简单而言OSAL实现了类似操作系统的某些功能但并不能称之为真正意义上的操作系统。 二、OSAL任务运行方式
弄明白了OSAL是何方神圣接下来我们将深入Z-Stack进一步研究OSAL。 为了方便我们使用Z-Stack所提供的GenericApp这个例程为例来进行分析。此例程的默认路径为
C:\\Texas Instruments\\ZStack-1.4.3-1.2.1\\Projects\\zstack\\Samples\\GenericApp。 首先我们去繁就简先来了解应用程序的运行方式。
在右侧工作空间窗口打开App文件夹我们可以看到三个文件分别是“GenericApp.c”、“GenericApp.h”、“OSAL_GenericApp.c”。我们整个程序所实现的功能都在这三个文件当中。
首先打开GenericApp.c这个文件。我们首先看到的是比较重要的两个函数:
GenericApp_Init和GenericApp_ProcessEvent。从函数名称上我们很容易得到的信息便是GenericApp_Init是任务的初始化函数而GenericApp_ProcessEvent则负责处理传递给此任务的事件。
大概浏览一下GenericApp_ProcessEvent这个函数我们可以发现此函数的主要功能是判断由参数传递的事件类型然后执行相应的事件处理函数。我们可以由此推断Z-Stack应用程序的运行机制如下图所示:
当有一个事件发生的时候OSAL负责将此事件分配给能够处理此事件的任务然后此任务判断事件的类型调用相应的事件处理程序进行处理。
明白了这个问题新的问题又摆在了我们的面前:OSAL是如何传递事件给任务的。 三、OSAL的事件传递机制
在试图弄清楚这个问题之前我们需要弄清楚另外一个十分基础而重要的问题。那就是如何向我们的应用程序中添加一个任务。
我们先来看看GenericApp是如何添加任务的。
我们打开OSAL_GenericApp.c文件。这里我们可以找到一个很重要的数组tasksArr和一个同样很重要的函数osalInitTasks。
TaskArr这个数组里存放了所有任务的事件处理函数的地址在这里事件处理函数就代表了任务本身也就是说事件处理函数标识了与其对应的任务。
osalInitTasks是OSAL的任务初始化函数所有任务的初始化工作都在这里面完成并且自动给每个任务分配一个ID。
要添加新任务我们需要编写新任务的事件处理函数和初始化函数然后将事件处理函数的地址加入此数组。然后在osalInitTasks中调用此任务的初始化函数。在此例中我们此前提到过的GenericApp_ProcessEvent这个函数被添加到了数组的末尾 GenericApp_Init这个函数在osalInitTasks中被调用。
值得注意的是TaskArr数组里各任务函数的排列顺序要与osalInitTasks函数中调用各任务初始化函数的顺序必须一直只有这样才能够保证每个任务能够通过初始化函数接收到正确的任务ID。
另外为了保存任务初始化函数所接收的任务ID我们需要给每一个任务定义一个全
局变量来保存这个ID。在GenericApp中GenericApp.c中定义了一个全局变量GenericApp_TaskID;并且在GenericApp_Init函数中进行了赋值 {
GenericApp_TaskID = task_id; }
这条语句将分配给GenericApp的任务ID保存了下来。 到此我们就给应用程序中完整的添加了一个任务。
我们回到OSAL如何将事件分配给任务这个问题上来
在OSAL_GenericApp.c这个文件中在定义TaskArr这个数组之后又定义了两个全局变量。
tasksCnt这个变量保存了当前的任务个数。
tasksEvents是一个指向数组的指针此数组保存了当前任务的状态。在任务初始化函数中做了如下操作 {
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); }
我们可以看出所有任务的状态都被初始化为0。代表了当前任务没有需要响应的事件。 紧接着我们来到了main()函数。此函数在ZMain文件夹的ZMain.c文件中。略过许多对当前来说并非重要的语句我们先来看osal_init_system()这个函数。在此函数中osalInitTasks()被调用从而tasksEvents中的所有内容被初始化为0。
之后在main()函数中我们进入了osal_start_system()函数此函数为一个死循环在这个循环中完成了所有的事件分配。
首先我们来看这样一段代码: {
do {
if (tasksEvents[idx]) {
break; }
} while (++idx < tasksCnt); }
当tasksEvents这个数组中的某个元素不为0即代表此任务有事件需要相应事件类
型取决于这个元素的值。这个do-while循环会选出当前优先级最高的需要响应的任务
{
events = (tasksArr[idx])( idx, events )
}
此语句调用tasksArr数组里面相应的事件处理函数来响应事件。如果我们新添加的任务有了需要响应的事件那么此任务的事件处理程序将会被调用。
就这样OSAL就将需要响应的事件传递给了对应的任务处理函数进行处理。 四、事件的捕获
不过接下来就有了更加深入的问题了事件是如何被捕获的直观一些来说就是tasksEvents这个数组里的元素是什么时候被设定为非零数来表示有事件需要处理的为了详细的说明这个过程我将以GenericApp这个例程中响应按键的过程来进行说明。其他的事件虽然稍有差别却是大同小异。
按键在我们的应用里面应该属于硬件资源所以OSAL理应为我们提供使用和管理这些硬件的服务。稍微留意一下我们之前说过的tasksArr这样一个数组它保存了所有任务的事件处理函数。我们从中发现了一个很重要的信息:Hal_ProcessEvent。HAL(Hardware Abstraction Layer)翻译为“硬件抽象层”。许多人在这里经常把将Z-Stack的硬件抽象层与ZigBee的物理层混为一谈。在这里我们应该将Z-Stack的硬件抽象层与ZigBee的物理层区分开来。硬件抽象层所包含的范围是我们当前硬件电路上面所有对于系统可用的设备资源。而ZigBee中的物理层则是针对无线通信而言它所包含的仅限于支持无线通讯的硬件设备。
通过这个重要的信息我们可以得出这样一个结论:OSAL将硬件的管理也作为一个任务来处理。那么我们很自然的去寻找Hal_ProcessEvent这个事件处理函数看看它究竟是如何管理硬件资源的。
在“HAL\\Commen\\ hal_drivers.c”这个文件中我们找到了这个函数。我们直接分析与按键有关的一部分。 {
if (events & HAL_KEY_EVENT) {
#if (defined HAL_KEY) && (HAL_KEY == TRUE) /* Check for keys */ HalKeyPoll();
/* if interrupt disabled, do next polling */ if (!Hal_KeyIntEnable) {
osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100); }
#endif // HAL_KEY
return events ^ HAL_KEY_EVENT; } }
在事件处理函数接收到HAL_KEY_EVENT这样一个事件后首先执行HalKeyPoll()函数。由于这个例程的按键采用查询的方法获取所以是禁止中断的于是表达式(!Hal_KeyIntEnable)的值为真。那么osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100)得以执行。osal_start_timerEx这是一个很常用的函数它在这里的功能是经过100毫秒后向Hal_TaskID这个ID所标示的任务(也就是其本身)发送一个HAL_KEY_EVENT事件。这样以来每经过100毫秒Hal_ProcessEvent这个事件处理函数都会至少执行一次来处理HAL_KEY_EVENT事件。也就是说每隔100毫秒都会执行HalKeyPoll()函数。 那么我们来看看HalKeyPoll函数到底在搞什么鬼! 代码中给的注释为: /* Check for keys */ HalKeyPoll();
于是我们推断这个函数的作用是检查当前的按键情况。进入函数一看果不其然。虽然这个函数很长
很复杂不过凭借着非凡的聪明才智我们还是十分清楚的明白了经过一系列的if语句和赋值语句在
接近函数末尾的地方 keys变量(在函数起始位置定义的)获得了当前按键的状态。最后有一个十分
重要的函数调用。
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
pHalKeyProcessFunction这个函数指针指向了哪个函数我们现在依然不清楚但是为了我们有个清
晰而不间断的思路我在这里先告诉大家。在这里调用的是
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
这个函数。此函数在“ZMain\\OnBoard .c”文件中可以找到。在这个函数中又调用了
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
在这个函数中按键的状态信息被封装到了一个消息结构体中(对于消息我们稍后再说)。最后有
一个极其重要的函数被调用了。
osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
与前面的pHalKeyProcessFunction相同我先直接告诉大家registeredKeysTaskID所指示的任
务正式我们需要响应按键的GenericApp这个任务。
那么也就是说在这里我们向GenericApp发送了一个附带按键信息的消息。在osal_msg_send函
数中
osal_set_event( destination_task, SYS_EVENT_MSG );
被调用它在这里的作用是设置destination_task这个任务的事件为SYS_EVENT_MSG。而
这个
destination_task正式由osal_msg_send这个函数通过参数传递而来的它也指示的是GenericApp
这个任务。在osal_set_event这个函数中有这样一个语句: {
tasksEvents[task_id] |= event_flag; }
至此刚才所提到的问题得到了解决。我们再将这个过程整理一遍。
首先OSAL专门建立了一个任务来对硬件资源进行管理这个任务的事件处理函数是
Hal_ProcessEvent。在这个函数中通过调用osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT,
100);这个函数使得每隔100毫秒就会执行一次HalKeyPoll()函数。HalKeyPoll()获取当前按键的状态
并且通过调用OnBoard_KeyCallback函数向GenericApp任务发送一个按键消息并且设置
tasksEvents中GenericApp所对应的值为非零。如此当main函数里这样一段代码 {
do
{
if (tasksEvents[idx])
{
break;
}
} while (++idx < tasksCnt); }
执行了以后GenericApp这个任务就会被挑选出来。然后通过
events = (tasksArr[idx])( idx, events )
这个函数调用其事件处理函数完成事件的响应。
cyril3 在GenericApp_ProcessEvent中通过 {
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_rec 现在我们回过头来处理我们之前
遗留下来的问题。
第一、pHalKeyProcessFunction这个函数指针为何指向了OnBoard_KeyCallback函数。
在HAL\\Commen\\ hal_drivers.c这个文件中我们找到了HalDriverInit这个函数在这个函数
中按键的初始化函数HalKeyInit被调用。在HalKeyInit中有这样的语句: {
pHalKeyProcessFunction = NULL; }
这说明在初始化以后pHalKeyProcessFunction并没有指向任何一个函数。那
pHalKeyProcessFunction是什么时候被赋值的呢
就在HalKeyInit的下方有一个这样的函数HalKeyConfig。其中有这样一条语句:
pHalKeyProcessFunction = cback
cback是HalKeyConfig所传进来的参数所以想要知道它所指向的函数必须找到其调用的地方。
经过简单的搜索我们不难找出答案。在main函数中有这样一个函数调用:InitBoard( OB_READY );此
函数中做了如下调用:
{
HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback); }
第二、registeredKeysTaskID为什么标识了GenericApp这个任务
由于OSAL是一个支持多任务的调度机制所以在同一时间内将会有多个任务同时运行。但是从逻辑
上来讲一个事件只能由一个任务来处理。按键事件也不例外。
那么如何向OSAL声明处理按键事件的任务是GenericApp呢 在GenericApp_Init(GenericApp的任务初始化函数)中有这么一个语句: {
RegisterForKeys( GenericApp_TaskID ); }
RegisterForKeys函数向OSAL声明按键事件将由GenericApp任务来处理。在RegisterForKeys
函数中:
{
registeredKeysTaskID = task_id; }
我想我不用再做多余的解释了聪明的您肯定可以理解。
五、消息队列
首先我需要想大家解释清楚消息与事件的联系。事件是驱动任务去执行某些操作的条件当系统产生
了一个事件将这个传递给相应的任务后任务才能执行一个相应的操作。但是某些事件在它发生的同时
又伴随着一些附加信息的产生。任务的事件处理函数在处理这个事件的时候还需要参考其附加信息。最
典型的一类便是按键消息它同时产生了一个哪个按键被按下了附加信息。所以在OnBoard_SendKeys
这个函数中不仅向GenericApp发送了事件还通过调用osal_msg_send函数向GenericApp发送
了一个消息这个消息记录了这个事件的附加信息。eive( GenericApp_TaskID ); }
获取了这样一个消息然后再进一步处理。
打开Cyril3AppCoordManage.c源文件添加如下代码:
byte Cyril3AppCoordManage_TaskID; void Cyril3AppCoordManage_Init( byte task_id ) {
Cyril3AppCoordManage_TaskID = task_id; }
UINT16 Cyril3AppCoordManage_ProcessEvent( byte task_id, UINT16 events ) {
return 0; }
在任务初始化函数中我们仅仅将分配给任务的ID保存下来。在事件处理函数中我们先什么也不
干。简单的返回0代表所有的事件已经处理完毕。不过即使这样谁也不能否认它是一个有效的事件处理
函数。接着我们在tasksArr数组里面加入Cyril3AppCoordManage_ProcessEvent这个事件处理函
数然后在osalInitTasks函数中调用任务的初始化函数。需要注意的是tasksArr数组里面各任务事件
处理程序的排列顺序必须要与osalInitTasks所调用任务初始化函数的顺序一致。
我们第一个任务的添加到此就完成了。
cyril3
2010-9-09 00:03:32
[i=s] 本帖最后由 cyril3 于 2010-9-9 00:16 编辑
四、加入网络
在把我们的设备加入网络之前我需要介绍几个概念。
1IEEE地址也叫64位长地址。在网络中每一个节点都是一个具体的物理设备。每个物理设备
都是独一无二的。如何从物理上标识网络上的设备呢我们引入了IEEE这样一个地址。IEEE地址是一个
64位地址这个地址是直接烧写进FLASH里面的它不会因为程序的运行而改变每个设备都有一个独
一无二的IEEE地址从而从物理上对设备加以区分。在PC机上每个网卡都有一个世界独一无二的MAC
地址道理是一样的。
2网络地址也叫16位短地址。当设备加入网络中以后为了在这个网络中对节点进行标识网络
会分配给设备一个16位短地址以此来标识此设备在网络中的地位。类似于我们常说的IP地址。
3ENDPOINT。很多资料将其翻译为“端点”我们不如也这么叫。不过问题的关键不是它如何称呼
而是如何认识它。我们来研究这样一个事实:outman在看我的帖子的同时他又使用QQ和别人聊天。假
设他的电脑IP地址为192.168.1.2。那么当他的QQ好友向他发送了一句话的时候这个信息里面包含
了目的IP地址所以通过TCP/TP协议可以到达outman的电脑。但是问题随之而来。当outman电脑
上的操作系统接收到此条信息时它将把这个信息交给浏览器(我们刚才说了他在看帖子所以肯定开
着浏览器)呢还是交给QQ操作系统通过怎么样的方法作出裁决呢显然只通过IP地址是没有办法
决定的所以这条消息除了包含IP地址以外还要告诉目的机这条消息应该交由哪个应用程序来处理。
于是端口(Port)的概念产生了。操作系统为应用程序提供了很多端口消息由IP地址到达操作系统再
由端口找到处理消息的应用程序。同样的道理在ZigBee的应用程序框架里(结构图请看《深入浅出Z-Stack 2006 OSAL多任务资源分配机制》)包含了最多240个应用程序对象每个应用程序对象在
OSAL中对应了一个任务当网络层接收到信息以后如何决定将此信息传递给哪个任务呢ENDPOINT决
定了传递方向于是我们可以说ENDPOINT的作用与TCP/IP协议中的端口的作用是一样的。
4Cluster。中文翻译为“簇”。我们如何去理解“簇”这个概念呢当一个任务接收到消息(这里所说
的消息是指无线网络之中的数据)之后一定会对消息进行处理。但是我们的应用肯定不会盲目的处理所
有消息消息一定会被分门别类。“簇”代表了消息的类型为与相同簇的消息具有相同的类型而这个类
型可以被用户自定义。
了解了这些我们可以考虑如何将设备加入网络中。
在我们的设备加电运行以后设备会对网络发送广播消息请求入网协调器确认其入网请求以后会给
它分配网络地址此设备将会作为网络中的一个节点运行。
设备加入网络由协议栈自动完成然而将任务作为一个ZigBee应用程序对象加入到ZigBee的应用
程序框架中是如何完成的呢
要将任务加入网络需要在任务重做一下两件事情:
1定义此任务作为ZigBee应用程序对象所需要的信息。
2将这些信息注册到应用程序框架中。
接下来我们就在我们的例子中实现它。
在Cyril3App.h中定义如下宏:
#define CYRIL3APPMANAGE_ENDPOINT 15
#define CYRIL3APPMANAGE_PROFID 0x0005
#define CYRIL3APPMANAGE_DEVICEID 0x0001
#define CYRIL3APPMANAGE_DEVICE_VERSION 0
#define CYRIL3APPMANAGE_FLAGS 0
#define CYRIL3APPMANAGE_MAX_CLUSTERS 1
#define CYRIL3APPMANAGE_CLUSTERID 1
CYRIL3APPMANAGE_ENDPOINT定义了当前任务的端点CYRIL3APPMANAGE_PROFID和
CYRIL3APPMANAGE_DEVICEID是一些有关识别的定义在学习实验中我们可以任意取值。
CYRIL3APPMANAGE_DEVICE_VERSION和CYRIL3APPMANAGE_FLAGS代表了硬件版本和自定义
标志我们定义为0不影响我们的应用开发。CYRIL3APPMANAGE_MAX_CLUSTERS定义了当前任
务的簇数量。CYRIL3APPMANAGE_CLUSTERID是我们自定义的一个簇ID取为1。
在Cyril3AppCoordManage.c中我们定义如下三个全局变量:
const uint16 Cyril3AppCoordManage_ClusterList[CYRIL3APPMANAGE_MAX_CLUSTERS] = {
CYRIL3APPMANAGE_ CLUSTERID
};
endPointDesc_t Cyril3AppCoordManage_epDesc;
const SimpleDescriptionFormat_t Cyril3AppCoordManage_SimpleDesc = {
CYRIL3APPMANAGE_ENDPOINT, CYRIL3APPMANAGE_PROFID,
CYRIL3APPMANAGE_DEVICEID,
CYRIL3APPMANAGE_DEVICE_VERSION,
CYRIL3APPMANAGE_FLAGS,
CYRIL3APPMANAGE_MAX_CLUSTERS,
(uint16 *)Cyril3AppCoordManage_ClusterList,
CYRIL3APPMANAGE_MAX_CLUSTERS,
(uint16 *)Cyril3AppCoordManage_ClusterList
};
Cyril3AppCoordManage_ClusterList保存了一个簇ID的列表所有有关于此任务的簇都需要在此
定义。
Cyril3AppCoordManage_epDesc是此任务的端点描述它定义了此任务作为ZigBee应用程序对
象的所有信息我们将会在初始化函数中为它赋值。然后将他注册到应用程序框架中。
Cyril3AppCoordManage_SimpleDesc是此任务的简单描述它保存了此任务的一些基本信息。具
体赋值我不再赘述。
在任务初始化函数中我们为端点描述赋值并且注册到应用程序框架中。
Cyril3AppCoordManage_epDesc.endPoint = CYRIL3APPMANAGE_ENDPOINT;
Cyril3AppCoordManage_epDesc.task_id = &Cyril3AppCoordManage_TaskID;
Cyril3AppCoordManage_epDesc.simpleDesc= (SimpleDescriptionFormat_t
*)&Cyril3AppCoordManage_SimpleDesc;
Cyril3AppCoordManage_epDesc.latencyReq = noLatencyReqs;
afRegister( &Cyril3AppCoordManage_epDesc );
这样我们的任务就顺利地作为ZigBee应用程序对象被加入到了应用程序框架中。这意味着我们可
以处理来自网络层的消息了。
五、处理消息
下面我们来试图实现这样一个功能:当设备加入网络之后点亮一个LED灯表示该节点成功加入
网络并处于就绪状态。
当设备成功加入网络以后ZigBee设备对象(ZDO)会给所有已经在应用程序框架中注册过的任务
(也就是作为ZigBee的应用程序对象存在的任务)发送一个“ZDO_STATE_CHANGE”消息消息的状
态指示了当前网络的状态。我们可以通过处理此消息时点亮LED等来实现这个功能。
当一个消息被发送给任务时“SYS_EVENT_MSG”事件会被传递给任务。“SYS_EVENT_MSG”这个
事件通知任务有一个消息等待处理。所以我们必须添加对于“SYS_EVENT_MSG”这个事件的响应然
后从消息队列中取出消息判断此消息是否为“ZDO_STATE_CHANGE”。在处理
“ZDO_STATE_CHANGE”消息时点亮LED灯。
在Cyril3AppCoordManage.c中我们定义如下全局变量:
devStates_t Cyril3AppCoordManage_NwkState=DEV_INIT;
Cyril3AppCoordManage_NwkState保存了当前节点的网络状态我们将他初始化为“未加入网 络”。
在Cyril3AppCoordManage_ProcessEvent函数中我们添加如下代码:
if ( events & SYS_EVENT_MSG )
{
afIncomingMSGPacket_t *MSGpkt;
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( Cyril3AppManage_TaskID );
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_STATE_CHANGE:
Cyril3AppCoordManage_NwkState = (devStates_t)MSGpkt->hdr.status;
if ( Cyril3AppCoordManage_NwkState == DEV_ZB_COORD )
{
HalLedSet( HAL_LED_1, HAL_LED_MODE_ON );
HalLedSet ( HAL_LED_2, HAL_LED_MODE_OFF );
HalLedSet ( HAL_LED_3, HAL_LED_MODE_OFF );
Cyril3AppCoordManage_ProcessZDOStateChange(
(devStates_t)
MSGpkt->hdr.status);
}
break;
default:
break;
}
osal_msg_deallocate( (uint8 *)MSGpkt );
MSGpkt = (afIncomingMSGPacket_t
*)osal_msg_receive( Cyril3AppManage_TaskID );
}
return (events ^ SYS_EVENT_MSG);
}
osal_msg_receive获取消息队列上相应任务的消息。osal_msg_deallocate释放消息所在的消息
缓冲区回收内存资源。
还是在Cyril3AppCoordManage.c中我们添加
Cyril3AppCoordManage_ProcessZDOStateChange函数:
void Cyril3AppCoordManage_ProcessZDOStateChange(devStates_t state) {
HalLedSet( HAL_LED_1, HAL_LED_MODE_ON ); }
正在阅读:
(通信 电子)ZigBee学习资料05-27
市人社局2022年上半年重点工作暨下半年就业帮扶工作计划08-01
抽调人员信访工作总结03-09
2015年高考语文模拟试卷103-11
2015年河北省4月份通用技术试题四07-25
起名常用字五行字库04-20
乡镇半年工作总结和下半年工作计划08-04
大家好08-26
大家11-07
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- 学习资料
- 通信
- ZigBee
- 电子
- 辽宁省朝阳市喀左蒙中2014届高三(上)第一次月考
- 《离骚》翻译 - (长太息以掩涕兮)
- 福师1208考试批次《学前儿童艺术欣赏》考试重点整理
- 服务流程
- 排列与组合同步练习
- “倾向值分析”考试(1)
- 上海市《普通幼儿园建设标准》(DG/TJ08-45-2005)
- 江苏省苏北四市连云港、徐州、淮安、宿迁四市2015届高三第一次调
- 煤矿总工程师技术手册(V1)
- abaqus重启动分析
- 郑州大学网上建筑制图与识图测试答案
- 物理化学论文 南开大学
- 通信原理试题
- 亳州师范高等专科学校升本建设方案
- 闽教版五年级下册英语Unit 1 Winter vacation同步练习及答案
- 2009-2010计算机考研真题及答案(含选择题解析)WORD高清晰版
- 2019年整理三峡库区三期地质灾害防治工程勘察技术要求 - 图文
- 历史人教版八年级上册《11课 北伐战争》教案
- YHC换绳车在立井多绳摩擦式提升系统钢丝绳更换工作中的应用
- 消毒灭菌设备项目可行性研究报告 - 图文