ble_app_blinky 是Nordic 为BLE从设备设计的官方示例,主要内容为用户自己设计service。
工程目录位于
\DeviceDownload\nRF5SDK153059ac345\nRF5_SDK_15.3.0_59ac345\examples\ble_peripheral\ble_app_blinky
工程目录如下

写在添加Service前的内容
以下内容摘抄博客园iini大佬的博客。有兴趣的搜索原文查看。
蓝牙软件的结构图,基本上任何蓝牙软件都是使用这个架构。

Application应用层:
我们开发一个蓝牙产品或者实现某种功能的设备时,开发主要部分位于应用层。
Profiles:初略理解为包含一个或者多个Service的集合。其中有蓝牙官方组织规定的Profiles,用户也可以自己设计属于自己的
专属Profiles。发布的GATT规范列表,包括警告通知(alert notificantion),血压测量(blood pressure),心率(heart rate),电池(battery)等等

非标准蓝牙任务规范profile,也称为私有任务。是供应商自定义的任务,在蓝牙SIG小组内未定义的任务规范,比如本例所谈的蓝牙LED灯任务。

协议栈:
通用访问规范(Generic Access Profile,GAP):
GAP是应用层能够直接访问BLE协议栈的最底层,它包括管理广播和连接事件的有关参数。GAP模块代表了所有蓝牙设备的共用基础功能,如传输,协议或者应用规范所使用的模式和访问过程。GAP的服务包括设备发现,连接方式,安全,认证,关联模型和服务发现等。

通用属性配置文件(Generic Attribute profile,GATT)
GATT层是传输真正数据所在的层。包括了一个数据传输和存储框架以及其基本操作。
GTTA定义了两类角色:
服务器(server)和客户端(client)

BLE采用了client/server (C/S) 架构来进行数据交互,C/S架构是一种非常常见的架构,在我们身边随处可见,比如我们经常用到的浏览器和服务器也是一种C/S架构,这其中浏览器是客户端client,服务器是服务端server,server比如淘宝服务器,提供商品信息,广告,社交等服务,而浏览器就是客户端,比如微软的IE,就可以用来请求这些服务,并使用server提供的服务。BLE与此类似,一般而言设备提供服务,因此设备是server,手机使用设备提供的服务,因此手机是client。比如蓝牙体温计,它可以提供 “体温” 数据服务,因此是一个server,而手机则可以请求“体温”数据以显示在手机上,因此手机是一个client。

服务是以数据为载体的,所以说server提供服务其实就是提供各种有价值的数据。

客户端要访问某一个数据,就发送一个request/请求(其实就是一条命令或者PDU),服务端再把该数据返回给客户端(一条response/响应命令或者PDU),这就是C/S架构。提供数据的一方为服务端,请求数据的一段为客户端。这里和蓝牙的主机从机是不同的概念。

蓝牙本质是一个通讯协议,主要目的是用来传输数据。所以一切围绕数据展开,是我认为的BLE学习方法。

数据是什么,单纯来说就是一些二进制比特位。比如蓝牙耳机的电量值,显然是手机作为客户端向蓝牙耳机服务端发送一个请求,要求耳机告诉手机你的电量值是多少。那么客户端,服务端,数据,请求,应答。都出现了。

把一些表示有逻辑意义的信息字节组织起来,就会变成一条条的数据,这样的数据称为 attribute,把attribute翻译成数据条目。
对于服务端而言,他提供的就时很多这样的数据条目,可以像表格一样一条一条的罗列出来。
假设一个班级有20个小朋友,每个人的年龄是服务端提供的数据。所以服务端可以提供20条数据。然而每个年龄如何喝小朋友的名字挂钩,这需要一些额外的信息。所以信息无法以非常单纯的数字形式存在。或包含很多用于表示信息类型,索引,权限的额外数据。

Attribute handle,Attribute句柄,16-bit长度。Client要访问Server的Attribute,都是通过这个句柄来访问的,也就是说ATT PDU一般都包含handle的值。用户在软件代码添加characteristic的时候,系统会自动按顺序地为相关attribute生成句柄。就像我们打开excle 最前面有个表格的行号,只要我们知道行号,就能找到这条数据,逻辑上的索引值。

Attribute type,Attribute类型,2字节或者16字节长。在BLE中我们使用UUID来定义数据的类型,UUID是128 bit的,所以我们有足够的UUID来表达万事万物。UUID有点类似身份证,每个人都有一个唯一身份证号。

Attribute value,就是数据真正的值,0到512字节长。
Attribute permissions,Attribute的权限属性,Open直接可以读或者写,No Access,禁止读或者写,Authentication,需要配对才能读或者写等等

一个应用包含很多数据条目,所有的attribute组成一个database,也称为attribute table,设备支持的服务不同,attribute table就不同。一个attribute table示例如下所示:

HANDLE 是一个自然增长的序列,在添加service的时候按照初始化顺序,一个一个添加到协议栈中。
TYPE:是一些UUID,有些SIG蓝牙官方组织会定义好,有些事用户自己定义。

ATT,全称attribute protocol(数据交互协议)。说到底,ATT是由一群ATT命令组成,就是上文所述的request(请求)和response(响应)命令,ATT也是蓝牙空口包中的最上层,也就是说,ATT就是大家对蓝牙数据包进行分析的最多的地方。

ATT命令,正式称谓ATT PDU(Protocol Data Unit,协议数据交互单元)包括4类:读,写,notify(通知)和indicate(指示)。这些命令又可以分成两种:如果它需要response,那么会在相应命令后面加上request;相反,如果它只需要ACK而不需要response,那么它的后面就不会带request。这里要特别强调一点,ATT所有命令都是“必达”的,也就是说每个命令发出去之后,会立马等ACK信息,如果收到了ACK包,发送方认为命令完成;否则发送方会一直重传该命令直到超时导致BLE连接断开。换句话说,只要你的BLE连接没有断开,那么你之前发送的数据包,不管它是用什么ATT PDU来发送的,它肯定被对方收到了。我估计很多人对此会产生疑问,因为他们经常碰到丢包的情况,其实大家经常碰到的“丢包”,不是空中把包丢了或者包在空中被干扰了,而是大家发送的代码写得有问题,导致你要发送的包没有被安全送达到协议栈射频FIFO中,从而出现所谓的“丢包”。以后大家碰到丢包情况,请先检查你的代码,保证你的数据包正确完整安全地送达到协议栈射频FIFO中,只要数据包放到了协议栈射频FIFO中,蓝牙协议栈就能保证该数据包“必达”对方。既然每个ATT命令都必达对方,那么还需要request类型的命令做什么?如果一个命令带有request后缀,那么发起方就可以收到命令的response包,这个response包在应用层是有回调事件的,而前述的ACK包在应用层是没有回调事件的。换句话说,不带request的命令,虽然协议栈底层确保了该命令必达对方,但应用层其实并不知道(私有实现方法除外),当你需要实现一个通信序列的时候,这种命令就显得不足了。而采用request/response方式的命令对,request命令发出去之后,必须等到相应的response命令回复才能进行下一步操作,比如发送下一个request命令,这样应用层可以严格按照规定逻辑执行一系列的操作,这个在很多应用场合是非常有用的。Request/response命令对还有一个副作用:大大降低通信的有效速率(吞吐率),因为request/response命令必须在不同的连接间隔中出现,也就是说,你在间隔1中发送了一个request命令,那么response包必须在间隔2或者稍后间隔中回复,而不能在间隔1中回复,这就导一个数据包的发送需要跨两个连接间隔甚至更多。而不带request后缀的ATT命令就没有这个限制,ACK可以在同一个连接间隔中回复,这样一个连接间隔中可以同时发出多个数据包,这样将大大提高通信速率。

不带request的命令只有2个:write command和notification,其余的命令都是带request:所有 read命令,所有write 命令,find命令以及indicate命令,完整的ATT命令(ATT PDU)列表如下所示:

GATT,全称generic attribute profile,对数据进行一般化/抽象化的子规范,说白了就是对数据进行逻辑化表达的规定。前面说过了,attribute是一条一条的数据,那么这条数据表示什么?如何对其进行分类?这就是GATT要做的事情,GATT将对数据赋予含义,并呈现一定的逻辑结构。Service和characteristic就是GATT层定义的,前面说过,server端提供服务,服务就是数据,而数据就是一条一条的attribute,而service和characteristic就是数据的逻辑呈现,或者说用户能看到的数据最终都转化为service和characteristic。比如,一个数据 “37” ,有可能是说体温“37度”,也有可能是说心率“37次”或者湿度“37%”,因此必须对数据进行分类和定义。


那service/characteristic和attribute之间到底是一个怎么样的关系?如前所述,service/characteristic是attribute的逻辑表现形式,而attribute是service/characteristic具体实现方式。尤其要注意的是,一条characteristic不是对应一条attribute,而是由多条attribute组成。虽然一个数据最有价值的部分是它的值(value),但是仅有value是不够的,比如27,到底是表示27°温度还是27%湿度;如果表示的是温度,那么它的单位是摄氏度还是华氏度,同时每个数据还有相应的读写属性以及权限属性,

因此一个characteristic包含三种类型的数据条目(attribute):characteristic声明条目(declaration attribute),characteristic值条目(value attribute)以及characteristic描述符条目(descriptor attribute)(一个characteristic可以有多个描述符条目)

由于一个service可以包含多个characteristic,characteristic declaration就是每个characteristic的分界符,解析时一旦遇到characteristic declaration,就可以认为接下来又是一个新的characteristic了,同时characteristic declaration还将包含value attribute的读写属性等。Characteristic value就是数据的值了,它也是一个单独的attribute,这个比较好理解就不再说了。Characteristic descriptor就是数据的额外信息,比如温度的单位是什么,数据是用小数表示还是百分比表示等之类的数据描述信息。Descriptor属于可选条目,也就是说,一个characteristic可以不包含任何一条descriptor。这里着重提一种特殊的descriptor:CCCD。一般而言,都是client来访问server的characteristic,即通过ATT读或者写PDU访问相关数据。如果server想直接把自己的characteristic的值告诉client,就需要通过notify或者indicate PDU,跟其他PDU相比,这2个PDU是由server自己决定什么时候开始传送,而不是被动接受client的命令请求。但client毕竟是客户啊,它得有自主权,所以引入了一个CCCD来帮助client控制server的行为。client可以通过禁止CCCD以不接收 notify或者indicate命令,client也可以通过使能CCCD以允许notify或者indicate命令。重新总结一下,当CCCD使能的情况下,server可以随时notify或者indicate数据给client;当CCCD禁止的时候,哪怕server有数据,它也不能notify或者indicate给client。这里强调一下,当characteristic具有notify或者indicate操作功能时,蓝牙规范要求必须为其添加CCCD attribute

所谓开发蓝牙应用程序,其实就是开发service和characteristic。

下载ble_app_blinky 历程到NRF52832 DK开发板上,使用NRF CONNECT 连接开发板。

可以看到"PRIMARY SERVICE"的字符其本身是一个条目,描述的是服务本身。
任何显示出来的数据都是来自服务端,所以看到的字符都是数据,都是数据条目。
BUTTON 表示 characteristic ,包含好几个数据条目 ,“BUTTON” 字符本身就是描述符。 type wei UUID.权限是NOTIFY,READ。descriptor 是可以配置的,说明可以使能CCCD。

本节最后,本节需要深刻理解数据条目Attribute,Characteristic,Service。
蓝牙开发本质是在方案商的SDK下,进行应用开发。一般的公司不会进行蓝牙协议栈的开发。

##第二部分

ble_app_blinky 官方示例 的开端,也是离不开数据。
那么顺着数据的思路在分析一下

首先一个application 就是提供一些数据给客户端,所以把这个历程所有的数据归类到一个服务中
这个服务有两个逻辑功能,一个是LED和按键。按照逻辑上可以把服务中所有的数据分为两类也就是说有两个Characteristic就可以满足需求

Characteristic 1:LED 能够进行数据的写入,服务端收到数据执行相应的LED开启关闭任务,视乎有个回调函数。
Value:LED state

Characteristic 2:BUTTON 主动告知客户端,开发板上按键的状态
Value:Press State

这样我们把这个application所有的数据已经用数据条目attribute的方式理了一次,对照一下下面的表格,看看还缺少什么


显然缺少了 attribute Type ----自己定义的UUID,因为SIG 小组并没有规定LED profile显然这是一个自定义的profile。

好的显然我们需要使用一个128位的自定义UUID,在SDK中如何设计呢

函数

uint32_t sd_ble_uuid_vs_add  (   ble_uuid128_t const *   p_vs_uuid,
uint8_t *   p_uuid_type
)

p_vs_uuid 指向一个128位的UUID数据
p_uuid_type 表示返回p_vs_uuid 指向数据的UUID类型,为输出值

这样我们定义好自己定义的UUID后就把attribute 内容准备好了,创建相应的变量就好。
所以我们要设计一个数据结构体,把application 的内容都涵盖进去。

首先一个应用可能包含多个Service所以需要有一个service_handle;
两个 Characteristic 。
一个UUID Type
LED需要根据写入的数据执行开关LED操作,这里需要一个led事件处理函数。

/**@brief LED Button Service structure. This structure contains various status information for the service. */
struct ble_lbs_s
{uint16_t                    service_handle;      /**< Handle of LED Button Service (as provided by the BLE stack). */ble_gatts_char_handles_t    led_char_handles;    /**< Handles related to the LED Characteristic. */ble_gatts_char_handles_t    button_char_handles; /**< Handles related to the Button Characteristic. */uint8_t                     uuid_type;           /**< UUID type for the LED Button Service. */ble_lbs_led_write_handler_t led_write_handler;   /**< Event handler to be called when the LED Characteristic is written. */
};

下一步
创建上面的结构体标量初始化,结构体变量
SDK用了一种比较特殊的方式,宏定义来创建。

BLE_LBS_DEF(m_lbs);
#define BLE_LBS_DEF(_name)                                                                          \
static ble_lbs_t _name;                                                                             \
NRF_SDH_BLE_OBSERVER(_name ## _obs,                                                                 \BLE_LBS_BLE_OBSERVER_PRIO,                                                     \ble_lbs_on_ble_evt, &_name)

创建了ble_lbs_t 结构体变量,注册了,lbs ble事件处理函数

加下来开始初始化服务

/**@brief Function for initializing services that will be used by the application.*/
static void services_init(void)
{ret_code_t         err_code;ble_lbs_init_t     init     = {0};nrf_ble_qwr_init_t qwr_init = {0};// Initialize Queued Write Module.qwr_init.error_handler = nrf_qwr_error_handler;err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);APP_ERROR_CHECK(err_code);// Initialize LBS.init.led_write_handler = led_write_handler;err_code = ble_lbs_init(&m_lbs, &init); //使用init对m_lbs进行初始化,init 为 0APP_ERROR_CHECK(err_code);
}
uint32_t ble_lbs_init(ble_lbs_t * p_lbs, const ble_lbs_init_t * p_lbs_init)
{uint32_t              err_code;ble_uuid_t            ble_uuid;ble_add_char_params_t add_char_params;// Initialize service structure.p_lbs->led_write_handler = p_lbs_init->led_write_handler;// Add service.ble_uuid128_t base_uuid = {LBS_UUID_BASE};err_code = sd_ble_uuid_vs_add(&base_uuid, &p_lbs->uuid_type); //保存返回的UUID类型,base uuid类型VERIFY_SUCCESS(err_code);// 服务名称 attribute  uuid,表示存在一条数据,用来表示服务本身ble_uuid.type = p_lbs->uuid_type;ble_uuid.uuid = LBS_UUID_SERVICE;//调用SDK服务添加APIerr_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid, &p_lbs->service_handle);VERIFY_SUCCESS(err_code);// 服务添加完成后,添加服务下面的特征属性characteristic,依照SDK的characteristic结构体进行// Add Button characteristic.memset(&add_char_params, 0, sizeof(add_char_params));add_char_params.uuid              = LBS_UUID_BUTTON_CHAR;add_char_params.uuid_type         = p_lbs->uuid_type;add_char_params.init_len          = sizeof(uint8_t);//数据初始长度add_char_params.max_len           = sizeof(uint8_t);//允许的最大长度add_char_params.char_props.read   = 1;              add_char_params.char_props.notify = 1;add_char_params.read_access       = SEC_OPEN;add_char_params.cccd_write_access = SEC_OPEN;err_code = characteristic_add(p_lbs->service_handle,&add_char_params,&p_lbs->button_char_handles);if (err_code != NRF_SUCCESS){return err_code;}//依次添加  characteristic// Add LED characteristic.memset(&add_char_params, 0, sizeof(add_char_params));add_char_params.uuid             = LBS_UUID_LED_CHAR;add_char_params.uuid_type        = p_lbs->uuid_type;add_char_params.init_len         = sizeof(uint8_t);add_char_params.max_len          = sizeof(uint8_t);add_char_params.char_props.read  = 1;add_char_params.char_props.write = 1;add_char_params.read_access  = SEC_OPEN;add_char_params.write_access = SEC_OPEN;return characteristic_add(p_lbs->service_handle, &add_char_params, &p_lbs->led_char_handles);
}

经过以上的步骤,创建了服务,和服务下面的特征属性,以及特征属性的数据条目。其关联是使用service_handle进行的。这样协议栈知道那几个characteristic是属于哪个服务的。

当协议栈需要通知应用程序一些有关它的事情的时候,协议栈事件就发生了,例如当写入特性或是描述符时。对应于本应用,你需要写入LED特性,为了让通知功能更好地工作,你需要保存连接句柄,通过这个句柄,你可以在连接事件和断开事件中实现某些操作。
作为 API的一部分,你可以定义一个函数 ble_lbs_on_ble_evt用来处理协议栈事件,可以使用简单的switch-case语句通过返回事件头部的id号来区分不同的事件,并进行不同的处理。

void ble_lbs_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{ble_lbs_t * p_lbs = (ble_lbs_t *)p_context;switch (p_ble_evt->header.evt_id){case BLE_GATTS_EVT_WRITE:on_write(p_lbs, p_ble_evt);break;default:// No implementation needed.break;}
}/**@brief Function for handling the Write event.** @param[in] p_lbs      LED Button Service structure.* @param[in] p_ble_evt  Event received from the BLE stack.*/
static void on_write(ble_lbs_t * p_lbs, ble_evt_t const * p_ble_evt)
{ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;if (   (p_evt_write->handle == p_lbs->led_char_handles.value_handle)&& (p_evt_write->len == 1)&& (p_lbs->led_write_handler != NULL)){p_lbs->led_write_handler(p_ble_evt->evt.gap_evt.conn_handle, p_lbs, p_evt_write->data[0]);}
}// led_write_handler 在服务初始化的时候进行了指定
/**@brief Function for handling write events to the LED characteristic.** @param[in] p_lbs     Instance of LED Button Service to which the write applies.* @param[in] led_state Written/desired state of the LED.*/
static void led_write_handler(uint16_t conn_handle, ble_lbs_t * p_lbs, uint8_t led_state)
{if (led_state){bsp_board_led_on(LEDBUTTON_LED);NRF_LOG_INFO("Received LED ON!");}else{bsp_board_led_off(LEDBUTTON_LED);NRF_LOG_INFO("Received LED OFF!");}
}
uint32_t ble_lbs_on_button_change(uint16_t conn_handle, ble_lbs_t * p_lbs, uint8_t button_state)
{ble_gatts_hvx_params_t params;uint16_t len = sizeof(button_state);memset(&params, 0, sizeof(params));params.type   = BLE_GATT_HVX_NOTIFICATION;params.handle = p_lbs->button_char_handles.value_handle;params.p_data = &button_state;params.p_len  = &len;return sd_ble_gatts_hvx(conn_handle, &params);
}

当协议栈接收到写事件的时候就会调用事件处理函数,p_evt_write->data[0] 为客户端写入的值。

到这代码视乎已经完成了。

再来看几个问题,客户端发起读的时候进行了哪些动作
读写操作也许就application开发另外一个重点了

在上面的led_write_handler函数中,我们处理了来自客户端的数据。通过事件的方式完成。
application 所有的操作都是建立在API和事件上的。事件是来自协议栈的反馈。调用API是应用层把自己相应操作发送给协议栈执行。
这所谓执行和反馈,构成了application的全部。

void ble_lbs_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{ble_lbs_t * p_lbs = (ble_lbs_t *)p_context;//添加这样的一行代码,观察协议栈发送给应用层的所有事件NRF_LOG_ERROR(" BLE EVENT :%x ,%d ",p_ble_evt->header.evt_id,p_ble_evt->header.evt_id);switch (p_ble_evt->header.evt_id){case BLE_GATTS_EVT_WRITE:on_write(p_lbs, p_ble_evt);break;default:// No implementation needed.break;}
}

Read
需要看是否进行了授权

1.无需授权的Read

协议栈会直接回复读取的值,没有应用层事件
2需要授权的read

ATT TABLE(在初始化service添加服务的时候,数据条目会逐条加入TABLE中)中存在的值。
协议栈收到Read 请求后悔产生RW AUTHORIZE 请求,并且告诉应用层Value值。
应用层可以重新设定返回的值app_value,如果权限通过则协议栈发送app_value 给客户端。
如果权限错误那么返回错误给客户端。

NRF 52832 ble_app_blinky 官方示例 part1相关推荐

  1. 实现3d图片移动_ThingJS官方示例(三):3D标记Marker动效定制化

    物联网3D可视化场景中,经常用到标注元素作为线路标绘.业务区域标绘,比如定位物联网设备或危险源位置,进行安全作业或者路径导航规划,远程解决难题. ThingJS的3D标记"Marker&qu ...

  2. 【Android 插件化】DroidPlugin 编译运行 ( DroidPlugin 简介 | 编译 DroidPlugin 官方示例 | 运行 DroidPlugin 官方示例 )

    文章目录 一.DroidPlugin 简介 二.DroidPlugin 编译运行 1.编译 DroidPlugin 官方示例 2.运行 DroidPlugin 官方示例 一.DroidPlugin 简 ...

  3. 【Android 热修复】运行 Tinker 官方示例 ( 处理 TINKER_ID 问题 | 编译 debug 包 | 修改 Gradle 脚本 | 生成 patch 包 | 热修复 )

    文章目录 一.下载官方示例源码 二.处理 TINKER_ID 问题 三.编译 debug 包 四.安装 APK 并运行 五.修改 Gradle 构建脚本中的文件名称 六.修改程序逻辑代码 七.生成 p ...

  4. Qt Dock Widgets 官方示例的翻译

    目录名字 Qt Dock Widgets 官方示例的翻译 Dock Widgets Example 介绍: MainWindow Class 定义: MainWindow Class 关联的相关头文件 ...

  5. AXI-IIC官方示例解析

    AXI-IIC官方示例解析 说明:本文是作者自己对Xilinx的AXI-IIC的官方示例的解析,如有错误望各位指正. 文章目录 AXI-IIC官方示例解析 前言 xiic_eeprom_example ...

  6. 微信小程序获取用户手机号--官方示例

    微信小程序获取用户手机号–官方示例 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNu ...

  7. mysql中示例库安装_MySQL 官方示例数据库安装

    虽然MySQL安装包中不像SQL Server和Oracle那样提供示例数据库,但官方也提供示例数据库以供学习使用. 官方示例数据库 下载地址 http://dev.mysql.com/doc/ind ...

  8. 玩转springboot2.x之搭建Thymeleaf官方示例程序

    1 thymeleaf 官方示例程序介绍 前面我已经介绍了如何在spirngboot2.0中使用freemarker和jsp,今天我们来说一下如何在springboot2.0中如何使用Thymelea ...

  9. 微软SQLServer官方示例项目部署-数据引擎和分析服务部分

    微软SQLServer每个版本都会带有相应的示例项目,从2000时的Foodmart到2005之后的Adventure Works,里面的设计方法和规范都有很多我们值得学习的地方.不仅是做普通的开发, ...

  10. 官方示例(十):网页开发3D粒子系统实现降雨效果 ThingJS

    简介:气温的变化会带动水汽条件的变化,带来降雪.降雨.降冰雹等奇异的天气现象,不仅仅是人的活动会受到影响,物联网设备管理.传感器监测及安全作业都要相应调整. 为了提前准备好预案工作,3D孪生场景的仿真 ...

最新文章

  1. 51nod 1225 余数之和(数论)
  2. 怎样用Java自制优秀的图片验证码?这样!
  3. c语言网络编程阻塞,c语言网络编程-设置非阻塞方式
  4. 启动oracle数据库工具,Oracle数据库常用工具
  5. [转] 在 Mac OS X 下编译 Objective-C 运行时
  6. ACM大牛总结的线段树专辑
  7. (转)如何看待IT对于证券行业的价值
  8. 这三个博弈论新趋势,正深刻影响深度强化学习道翰天琼认知智能未来机器人接口API
  9. lammps教程:薄膜渗透过滤模拟(1)
  10. 使用JsonArray.fromObject()需要注意的事项
  11. Yii路由之LimeSurvey去掉烦人的/index.php/*
  12. 计算机盘快捷键,电脑键盘快捷键全解
  13. 活动图与流程图区别以及各自画法
  14. u3d 镜面反射的效果
  15. Win XP系统无法关机时如何强制软关机
  16. redenvelope php,Red Envelope (红包)
  17. 海底捞的启示(4):员工成长与职业生涯
  18. 零基础入门金融风控-贷款违约预测
  19. C语言练习之温度转换
  20. ZYNQ - 嵌入式Linux开发 -05- Linux C编程和Makefile

热门文章

  1. C语言二进制与十进制之间的转换
  2. timestamp和datetime的区别
  3. 美文听力:别错过机会
  4. java实现五子棋获胜判断
  5. 在已有win7系统的基础上重装win10系统
  6. OneNote2016电脑端修改笔记本名称网页端不同步解决办法
  7. 爬取虎嗅 5 万篇文章告诉你怎么样取标题
  8. 【Redis学习笔记】redis-trib.rb命令详解
  9. 项目合同管理:合同分类、费用支付方式、违约责任承担方式、签订注意事项、合同索赔流程
  10. 北京地区常用dns地址解析速度快