花了好久写的...感觉还不错的呢...如果看,请细看...Mua~

Z-Stack协议栈基础和数据传输实验

一、实验目的

终端节点将数据无线发送到协调器,协调器通过串口将数据发送到PC端,并在屏幕上显示出来。串口优化把有线串口传输改为无线蓝牙传输。

二、实验平台

  硬件:2个zigbee节点,1个编译器,1根方口转USB数据线,一个蓝牙模块

  软件:实验基于SampleApp工程进行。

三、实验步骤

  1. 串口初始化代码
  2. 发送部分代码
  3. 接收部分代码

四、协议栈基础

  做实验之前先了解一点关于协议栈的基础知识吧~

  什么是协议栈?我们知道使用Zigbee一般都要进行组网、传输数据。可想而知其中的代码数量是非常庞大的,如果我们每次使用zigbee都需要自己写所以代码的话,会非常麻烦。因此就有了协议栈。可以说它是一个小型的操作系统,把很多通信、组网之类的代码都封装起来了。我们要做的只是通过调用函数来实现我们的目的。

  来看一下协议栈的工作流程图(图1)。然后我会对照流程图对协议栈进行简单的分析。

图1

  我们就从流程图的“开始”开始分析吧~

  打开工程文件SampleApp,main函数是程序执行的开始,我们要先找到它。Main函数在ZMAin文件夹的ZMain.c下,打开它,找到main函数。

  1 int main( void )
  2
  3 {
  4
  5   // Turn off interrupts
  6
  7   //关闭所有中断
  8
  9   osal_int_disable( INTS_ALL );
 10
 11
 12
 13   // Initialization for board related stuff such as LEDs
 14
 15   //初始化系统时钟
 16
 17   HAL_BOARD_INIT();
 18
 19
 20
 21   // Make sure supply voltage is high enough to run
 22
 23   //检测芯片电压是否正常
 24
 25   zmain_vdd_check();
 26
 27
 28
 29   // Initialize board I/O
 30
 31   //初始化外设
 32
 33   InitBoard( OB_COLD );
 34
 35
 36
 37   // Initialze HAL drivers
 38
 39   //初始化芯片各硬件模块
 40
 41   HalDriverInit();
 42
 43
 44
 45   // Initialize NV System
 46
 47   //初始化flash存储器
 48
 49   osal_nv_init( NULL );
 50
 51
 52
 53   // Initialize the MAC
 54
 55   //初始化MAC层
 56
 57   ZMacInit();
 58
 59
 60
 61   // Determine the extended address
 62
 63   //确定IEEE 64位地址
 64
 65   zmain_ext_addr();
 66
 67
 68
 69 #if defined ZCL_KEY_ESTABLISH
 70
 71   //Initialize the Certicom certificate information.
 72
 73   zmain_cert_init();
 74
 75 #endif
 76
 77
 78
 79   // Initialize basic NV items
 80
 81   //初始化非易失变量
 82
 83   zgInit();
 84
 85
 86
 87 #ifndef NONWK
 88
 89   // Since the AF isn't a task, call it's initialization routine
 90
 91   afInit();
 92
 93 #endif
 94
 95
 96
 97   // Initialize the operating system
 98
 99   //初始化操作系统***********************************初始化重点
100
101   osal_init_system();
102
103
104
105   // Allow interrupts
106
107   //允许中断使能
108
109   osal_int_enable( INTS_ALL );
110
111
112
113   // Final board initialization
114
115   //初始化按键
116
117   InitBoard( OB_READY );
118
119
120
121   // Display information about this device
122
123   //显示设备信息
124
125   zmain_dev_info();
126
127
128
129   /* Display the device info on the LCD */
130
131 #ifdef LCD_SUPPORTED
132
133   zmain_lcd_init();
134
135 #endif
136
137
138
139 #ifdef WDT_IN_PM1
140
141   /* If WDT is used, this is a good place to enable it. */
142
143   WatchDogEnable( WDTIMX );
144
145 #endif
146
147
148
149   // No Return from here
150
151   // 执行操作系统,进去后不会返回************************运行重点
152
153   osal_start_system();
154
155
156
157   return 0;  // Shouldn't get here.
158
159 } 

main();

  浏览一下main函数可以看到一开始都是各种初始化函数,即对应流程图中的“各种初始化函数”。初始化中我们需要注意的是“osal_init_system();”初始化操作系统函数。等一下会对它进行说明。继续看下去,“osal_start_system();”这是执行操作系统函数,对应流程中的“运行操作系统”。注意这个函数进去之后是不会再返回的。总结main函数就是初始化和执行操作系统两个部分。

  我们再来分析一下“osal_init_system();”这个函数,它的功能是初始化操作系统。我们go to definition看一下这个函数的代码。

 1 uint8 osal_init_system( void )
 2
 3 {
 4
 5   // Initialize the Memory Allocation System
 6
 7   // 初始化内存分配系统
 8
 9   osal_mem_init();
10
11
12
13   // Initialize the message queue
14
15   // 初始化消息队列
16
17   osal_qHead = NULL;
18
19
20
21   // Initialize the timers
22
23   // 初始化定时器
24
25   osalTimerInit();
26
27
28
29   // Initialize the Power Management System
30
31   // 初始化电源管理系统
32
33   osal_pwrmgr_init();
34
35
36
37   // Initialize the system tasks.
38
39   // 初始化系统任务**********************************重点
40
41   osalInitTasks();
42
43
44
45   // Setup efficient search for the first free block of heap.
46
47   osal_mem_kick();
48
49
50
51   return ( SUCCESS );
52
53 }

osal_init_system();

  浏览这个函数我们可以看到其中依旧是各种初始化函数。重点观察“osalInitTasks();”这个函数,函数功能是初始化任务系统,继续go to definition,查看该函数。

 1 void osalInitTasks( void )
 2
 3 {
 4
 5   uint8 taskID = 0;
 6
 7
 8
 9   //分配缓冲区内存,返回指针
10
11   tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
12
13   //设置所分配的内存空间单元值为0
14
15   osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
16
17
18
19   // 任务优先级由高到低依次排列,高优先级对应taskID小
20
21   macTaskInit( taskID++ );  //0 不用考虑
22
23   nwk_init( taskID++ );     //1 |
24
25   Hal_Init( taskID++ );     //2 |
26
27 #if defined( MT_TASK )
28
29   MT_TaskInit( taskID++ );
30
31 #endif
32
33   APS_Init( taskID++ );     //3 |
34
35 #if defined ( ZIGBEE_FRAGMENTATION )
36
37   APSF_Init( taskID++ );
38
39 #endif
40
41   ZDApp_Init( taskID++ );   //4 需要考虑
42
43 #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
44
45   ZDNwkMgr_Init( taskID++ );
46
47 #endif
48
49   SampleApp_Init( taskID ); //5 需要考虑*************************重点
50
51                             //通常在这里初始化自己的东西
52
53 }

osalInitTask();

  通过注释我们可以知道这个函数也是拿来初始化的,可以里面的代码有点难以理解......这里我们需要先知道一点,后面会提到,这里先说明下。额,因为这个是我自己的理解,所以部分描述起来可能不是很专业,能懂这个意思就好了,以后专业起来了再回来修改......协议栈采用任务机制,然后使用轮询的方式处理任务。就是说在空闲的时候它从优先级高的任务开始,一个个检查是否有任务要处理,有则处理这个任务,没有则继续循环检测。

  好嘞~就是这样!那么再来看这个函数,它的作用就是按“任务”的优先级给它们发一个ID号,发的同时呢又对这个任务进行初始化。需要注意的是任务优先级越高,它的ID号越小!然后上面那些我们全都不用考虑,需要考虑的是最后两个函数(原来我们能操作的优先级最低呀......)。嗯...感觉go to definition好久了...就不继续看下去啦,之后再详细解读这两个函数吧~

  这样子初始化的函数算是解释完了,我们回到main函数,继续看下一个函数“osal_start_system();”执行操作系统函数!来来来,继续go to definition找到它本尊。

 1 void osal_start_system( void )
 2
 3 {
 4
 5 #if !defined ( ZBIT ) && !defined ( UBIT )
 6
 7   for(;;)  // Forever Loop
 8
 9 #endif
10
11   {
12
13     osal_run_system();
14
15   }
16
17 }

osal_start_system();

  嗯哼,找到“osal_run_system();”我们继续......

 1 void osal_run_system( void )
 2
 3 {
 4
 5   uint8 idx = 0;
 6
 7
 8
 9   osalTimeUpdate();        //扫描哪个事件被触发了,置相应标志位
10
11   Hal_ProcessPoll();
12
13
14
15   //从0(最高优先级)开始检索是否有任务需要处理
16
17   //有则立即跳出循环,没有等到检索完再跳出循环
18
19   do {
20
21     if (tasksEvents[idx])  // Task is highest priority that is ready.
22
23     {
24
25       break;
26
27     }
28
29   } while (++idx < tasksCnt);
30
31
32
33   //分析索引号,对任务进行处理,没有任务就跳过
34
35   if (idx < tasksCnt)
36
37   {
38
39     uint16 events;
40
41     halIntState_t intState;
42
43
44
45     HAL_ENTER_CRITICAL_SECTION(intState);    //进入临界区,保护
46
47     events = tasksEvents[idx];               //提取任务
48
49     tasksEvents[idx] = 0;  // Clear the Events for this task.清除任务
50
51     HAL_EXIT_CRITICAL_SECTION(intState);     //退出临界区
52
53
54
55     activeTaskID = idx;
56
57     events = (tasksArr[idx])( idx, events ); //通过指针调用任务处理函数********重点
58
59     activeTaskID = TASK_NO_TASK;
60
61
62
63     HAL_ENTER_CRITICAL_SECTION(intState);    //进入临界区
64
65     tasksEvents[idx] |= events;  // Add back unprocessed events to the current task.
66
67                                              //保存未处理事件
68
69     HAL_EXIT_CRITICAL_SECTION(intState);     //退出临界区
70
71   }
72
73 #if defined( POWER_SAVING )
74
75   else  // Complete pass through all task events with no activity?
76
77   {
78
79     osal_pwrmgr_powerconserve();  // Put the processor/system into sleep
80
81   }
82
83 #endif
84
85
86
87   /* Yield in case cooperative scheduling is being used. */
88
89 #if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0)
90
91   {
92
93     osal_task_yield();
94
95   }
96
97 #endif
98
99 }

osal_run_system();

  这里就是我之前说的轮询的地方啦~这里就说下我的理解吧......但是不确定对不对......大致思想应该是对的......

  先把工作分成两部分,一部分是任务请求,有任务请求了就把相应标志位置1。另一部分就是我们看到的这个函数。在函数开头读一下任务请求的寄存器(也许不是寄存器,就那个意思),然后从最高优先级依次检索是不是有任务请求。只要有任务请求,就进入处理任务请求部分(就是“if (idx < tasksCnt)”这个if语句里面的内容),没有则继续循环。处理任务请求部分中需要注意两点:1. 它在把高优先级任务处理完之后会继续检测是否还有任务请求,直到把使用任务请求处理完毕。2. 处理完一个任务之后它会清除该任务的标志位。

  咳,不知道你们有没有看懂......然后这里面的重点函数呢就是“events = (tasksArr[idx])( idx, events );”这一句。先看一看tasksArr[]这个数组的定义。

 1 const pTaskEventHandlerFn tasksArr[] = {
 2
 3   macEventLoop,
 4
 5   nwk_event_loop,
 6
 7   Hal_ProcessEvent,
 8
 9 #if defined( MT_TASK )
10
11   MT_ProcessEvent,
12
13 #endif
14
15   APS_event_loop,
16
17 #if defined ( ZIGBEE_FRAGMENTATION )
18
19   APSF_ProcessEvent,
20
21 #endif
22
23   ZDApp_event_loop,
24
25 #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
26
27   ZDNwkMgr_event_loop,
28
29 #endif
30
31   SampleApp_ProcessEvent
32
33 };

pTaskEventHandlerFn tasksArr[]

  有没有发现它和函数osalInitTasks();在同一个文件里面!有没有发现它就在osalInitTasks()这个函数的上面!再仔细看一看,有没有发现它定义成员变量名的顺序和下面初始化函数的顺序是一样的!这样说估计还是云里雾里的吧...(因为写到这里我也还是没有完全懂...)我再说明白点......之前不是说每个任务都有一个ID号嘛,优先级从0开始的,而数组里面第一位的索引号也是0,就是说任务ID号和数组索引号相对应,那么利用任务ID号就可以在数组里面找到相应的任务。还有很神奇的一点,至少我是怎么觉得的...之前看这行代码感觉非常难以理解......“events = (tasksArr[idx])( idx, events ); ”。刚刚想明白的,原来那个数组的类型是一个函数!也就是说通过任务ID找到相应进行任务处理的函数!这样你们有没有明白?不懂留言......有人看吗?笑......

  终于,协议栈的分析工作完成了......会不会觉得很乱?看看下面的流程图再来回顾整理一下吧~

图2

五、实验过程和说明

  1. 串口初始化代码

  ①在协议栈中,用户自己添加代码的地方基本为App这个文件夹。打开其中的文件SampleApp.c,在INCLUDES部分添加代码

  #include "MT_UART.h"

图3

  操作说明:协议栈中关于串口封装的文件有两个,一个是HAL->Target->CC2530EB->Drivers->hal_uart.c,另一个是MT->MT_UART.c。这两个文件有什么区别呢?打开文件研究一下。首先,在MT_UART.c中有include “hal_uart.h”,所以写头文件只要写”MT_UART.h”即可。然后,仔细看代码,分析hal_uart.c这个文件只要是对不同串口类型的相应操作进行选择,MT_UART.c这个文件则是对任意串口的操作。就是说MT_UART.c这个文件更底层一点。

  ②同样SampleApp.c文件中,找到函数void SampleApp_Init( uint8 task_id ),在其中加入串口初始化代码

  /************串口初始化******************/MT_UartInit();                      //串口初始化
MT_UartRegisterTaskID(task_id);     //登记任务号

图4

  操作说明:串口初始化就不说了。登记任务号就是把串口事件通过task_id登记在SampleApp_Init();中。之前我们有提到说SampleApp_Init();函数很重要,就是分配ID号,它还是优先级最低的那个。把这个函数的ID号给串口就是告诉串口我是在这个函数里面初始化的,相应的我的任务优先级是最低的......

  ③更改串口初始化配置。

  在上图所示的MT_UartInit();处go to definition,进入MT_UartInit()函数(如图5)。找到其中的MT_UART_DEFAULT_BAUDRATE,go to definition后将波特率设置为115200(如图6)。

  回到图3位置,找到MT_UART_DEFAULT_OVERFLOW,go to definition将参数设为FALSE(如图7)。

图5

图6

图7

  操作说明:修改波特率就不解释啦。

  #define MT_UART_DEFAULT_OVERFLOW       FALSE

  这行代码是打开串口流控的意思。因为我们串口通讯是两根线的,必须把它关闭。

  2. 发送部分代码

  ①打开SampleApp.c,找到SampleApp事件处理函数SampleApp_ProcessEvent()。

  补充一点,我们可以在SampleApp下添加自己的事件,每个事件有自己的事件号。事件号是16位的,但是每个事件号只允许占16位中的1位,也就是说最多有16个事件。

  我们先浏览一下代码,大致功能是分析传递进来的事件号,触发相应事件。感觉这个和任务号处理模式还是挺像的。我们需要关注的是“系统消息事件”被触发之后。即 “if ( events & SYS_EVENT_MSG )”语句之后的部分。先看第一行:

  MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );

  这一行代码实现功能是获取系统消息数据。我们可以自己去查看里面的定义。afIncomingMSGPacket是包含整个消息内容的结构体类型。

  之后的选择语句则是根据消息中的信息对数据进行相应处理。我们需要关注如下代码(图8)

图8

  弄了半天,原来还在初始化......意思是网络状态发送变化时(其实就是打开网络),就对数据发送进行初始化。看下这三个参数,第一个是任务号,不重复啦。第二个是事件号,这个也说过啦,每个事件只占1位哦!第三个是设置时间,就是规定你多久发一次信息!这里我们预设SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT = 5000,这个值可以自行修改,数值单位是毫秒,就是说这个程序是5秒发送一次数据。

  ②设置发送内容,自动周期性发送。在同一个函数下找到如下代码(图9)

图9

  如果触发周期性数据发送部分(就是说5秒过去了,要发送信息了),就执行SampleApp_SendPeriodicMessage()这个函数。这个函数是重点哦,里面放我们需要发送的数据。继续go to definition......

  找到该函数后对该函数做如下修改(如图10)。

图10

  我们来看一下AF_DataRequest()这个函数,通过上下文我们就可以知道这个函数一定就是决定发送数据内容的啦。我们需要关注的是其中第3、4、5个参数,第3个参数的作用是和接收方建立联系,这里定义SAMPLEAPP_PERIODIC_CLUSTERID=1,如果协调器收到一个数据包,获取里面的这个标号,为1则证明这个数据包是以周期性广播方式进来的。第4个参数表示发送数据的长度,第5个参数为需要发送的数据的指针。

进行到这里发送数据部分已经结束啦~~~

  3. 接收部分代码

  在SampleApp.c下找到函数“void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )”,在“case SAMPLEAPP_PERIODIC_CLUSTERID:”下面一行添加代码(如图11):

 HalUARTWrite(0, "I get data!\n", 12);HalUARTWrite(0, &pkt->cmd.Data[0], pkt->cmd.DataLength);HalUARTWrite(0, "\n", 1);

图11

  操作说明:我们先看一下这个条件语句“case SAMPLEAPP_PERIODIC_CLUSTERID:”。这个就是在发送部分设置的表示周期性发送数据的编号。看吧,这里就用上了~

在添加代码的这个地方,我们可以对收到的数据进行处理(不局限于串口发送)。这里的三行代码都是串口发送的,不再多说什么啦。

  重点看一下“afIncomingMSGPacket_t *pkt”。所有的数据和信息都在函数传进来的afIncomingMSGPacket里面,查看这个定义

 1 typedef struct
 2
 3 {
 4
 5   osal_event_hdr_t hdr;     /* OSAL Message header */
 6
 7   uint16 groupId;           /* Message's group ID - 0 if not set */
 8
 9   uint16 clusterId;         /* Message's cluster ID */
10
11   afAddrType_t srcAddr;     /* Source Address, if endpoint is STUBAPS_INTER_PAN_EP,
12
13                                it's an InterPAN message */
14
15   uint16 macDestAddr;       /* MAC header destination short address */
16
17   uint8 endPoint;           /* destination endpoint */
18
19   uint8 wasBroadcast;       /* TRUE if network destination was a broadcast address */
20
21   uint8 LinkQuality;        /* The link quality of the received data frame */
22
23   uint8 correlation;        /* The raw correlation value of the received data frame */
24
25   int8  rssi;               /* The received RF power in units dBm */
26
27   uint8 SecurityUse;        /* deprecated */
28
29   uint32 timestamp;         /* receipt timestamp from MAC */
30
31   uint8 nwkSeqNum;          /* network header frame sequence number */
32
33   afMSGCommandFormat_t cmd; /* Application Data */
34
35 } afIncomingMSGPacket_t;

afIncomingMSGPacket_t

  它是一个结构体,里面包含了数据包的所以内容,这里就不说啦,想知道的自己翻译下注释吧~我们重点关注其中的afMSGCommandFormat_t cmd。查看它的定义

typedef struct{uint8   TransSeqNumber;uint16  DataLength;              // Number of bytes in TransData
uint8  *Data;} afMSGCommandFormat_t;

afMSGCommandFormat_t

  哦哦,这个里面就有我们传送的数据内容啦!其中的DataLength就是数据长度,Data就是数据内容的指针啦。

  我们再看回上去,HalUARTWrite(0, &pkt->cmd.Data[0], pkt->cmd.DataLength);这个代码就是把收到的数据发送给串口啦

  4. 程序烧录

  这里有个注意事项!下载的时候需要选择模式!如图12,CoordinatorEB模式下载到协调器(连接电脑的那个!),EndDeviceEB模式下载到终端模块!

图12

  至此,无线数据传输实验就结束啦~

  没有记住?没关系!下面就是总结回顾整个流程啦~

六、流程分析

图13

  看着这个流程图有没有觉得更加方便理解呢?

七、串口优化

  这个推荐去看我之前写的 「51单片机」蓝牙从机基本使用方法

  地址:http://www.cnblogs.com/Donut/p/4054348.html

  嘻嘻 给自己打个广告~

  操作方式一模一样,只是波特率配置是115200,然后串口么连接zigbee的UART0(仅限这个实验,之后你定义了哪个串口就连哪个串口的呗~)

转载于:https://www.cnblogs.com/Donut/p/4138986.html

「ZigBee模块」协议栈-Z-Stack协议栈基础和数据传输实验相关推荐

  1. 「 Unity 3D」是什么?零基础如何快速入门?

    一句话说明下Unity是什么,Unity是一个开发游戏,主要用于手机游戏开发的引擎,什么是引擎,引擎就是工具的意思. 零基础想自学从哪开始?一般我们学习unity3d游戏开发是要先从C++开始学起,如 ...

  2. Meta开发了一个AI模型,尝试解决维基百科的「性别偏见」问题

    来源:SiliconANGLE 出品:科技行者 撰文:海外来电 图片:海外来电 为了解决两性人物传记比例失衡的情况,Meta操碎了心. 维基百科一直是全球访问量Top 10的网站,是许多人搜索历史人物 ...

  3. await原理 js_「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能

    本周,Nodejs v14.3.0 发布.这个版本包括添加顶级 Await.REPL 增强等功能. REPL 增强 通过自动补全改进对 REPL 的预览支持,例如,下图中当输入 process.ver ...

  4. canvas换图时候会闪烁_基于Canvas实现的高斯模糊(上)「JS篇」

    作者:iNahoo 转发链接:https://mp.weixin.qq.com/s/5TxPjznpEBku_ybSMBdnfw 目录 基于Canvas实现的高斯模糊(上)「JS篇」本篇 基于Canv ...

  5. node 压缩图片_推荐10个常用的图片处理小帮手(下)「值得收藏」

    作者: semlinker 转发链接:https://mp.weixin.qq.com/s/i3ynTtPJOECoAYfqHFoo3Q 前言 本文给小伙伴们隆重介绍用于图片处理的十个 「" ...

  6. 那些「业余创业者」,是怎么被投资人悄悄贴上标签的?

    不知不觉,我和顺为资本的投资合伙人周航牵头发起的「未来前沿」创始人工坊已经办三期了. 前优酷土豆联席总裁魏明从第二期开始也加入了「班主任」的行列,我有幸和这两位一起,觉得自己学到很多. 魏明对创业者充 ...

  7. 分布式系统关注点(12)——「无状态」详解

    如果这是第二次看到我的文章,欢迎右侧扫码订阅我哟~  ? 本文长度为2728字,建议阅读8分钟. 坚持原创,每一篇都是用心之作- 前面聊完的2个章节「数据一致性」和「高可用」其实本质是一个通过提升复杂 ...

  8. dreamweaver 正则表达式为属性值加上双引号_「前端篇」不再为正则烦恼

    作者:李一二 转发链接:https://mp.weixin.qq.com/s/PmzEbyFQ8FynIlXuUL0H-g 前言 有不少朋友都为写正则而头疼,不过笔者早已不为正则而烦恼了.本文分享一些 ...

  9. 争夺2nm芯片王冠!台积电即将建厂量产,「牙膏厂」英特尔发布5年计划

    本文转载自 新智元 在前天的发布会上,英特尔踌躇满志,誓要摆脱「牙膏厂」之名! 「全面革新芯片制程工艺,五年干翻对手」! 不料发布会刚结束,英特尔就被台积电打脸了. 怎么回事? 我们先看看英特尔说了什 ...

最新文章

  1. Java复习-线程之间的通信与同步
  2. 在整个数据库中查找包含某关键字的所有存储过程
  3. 记一次 Python Web 接口优化,性能提升25倍!
  4. vue 动态组件组件复用_真正的动态声明性组件
  5. 自定义条件查询_数据查询不止有vlookup函数,自定义zlookup函数查询操作更高效...
  6. 安装kenlm出现问题的解决方案gcc g++
  7. python数据运算
  8. 知道答案吗?知道为什么是这个答案吗?
  9. java8如何兼容java7_尽管使用Java 8功能,项目如何支持Java 7
  10. 浅谈Java的Nio以及报Connection refused: no further information异常原因?
  11. Java中大数据数组,Java基础学习笔记之数组详解
  12. vue-i18n使用ES6语法以及空格换行问题
  13. 矩阵计算器——大一c++大作业回顾
  14. 2014 抢票工具 纯java
  15. python PIL库中的getpixel函数
  16. python做一个登录注册界面_python做一个登录注册界面的方法
  17. vue中的@符号的是什么意思
  18. 关于 京东推广-京准通 脑图分享
  19. 霍尔效应传感器的典型应用场合解析
  20. 他,如此与众不同 ——法国《世界报》专版聚焦DJ

热门文章

  1. 2020国家公务员考试专业目录
  2. 快速将小程序生成APP八步走!
  3. 飞桨图像分类入门——多种网络模型在手写数字识别的应用
  4. 免费在线流程图、网络拓扑图等制图 --- Process On --- https://www.processon.com
  5. 我的中国象棋游戏程序单机版
  6. 动态轮播图,支持gif动态轮播
  7. Word转PDF软件哪个好?这款软件一定要试试
  8. multiple of 4 can be compressed to DXT5 format
  9. 分发文件到服务器,Linux 把文件分发到不同服务器
  10. 大数据测试回放视频-小强测试内部学员技术分享