首页 文章 精选 留言 我的

精选列表

搜索[个人博客],共10006篇文章
优秀的个人博客,低调大师

博客大赛】蓝牙BLE详解

一、什么是蓝牙? 蓝牙是一种短距的无线通讯技术,可实现固定设备、移动设备之间的数据交换。一般将蓝牙3.0之前的BR/EDR蓝牙称为传统蓝牙,而将蓝牙4.0规范下的LE蓝牙称为低功耗蓝牙。 很多人对蓝牙的认识还很局限于手机领域,其实蓝牙的应用已经远远不止于此。过去几年里,蓝牙的增长量就达到了80%,当然,低功耗蓝牙的出现也起到关键的作用,相信未来蓝牙会开创一个可交互的物联世界。 蓝牙4.0标准包括传统蓝牙模块部分和低功耗蓝牙模块部分,是一个双模标准。低功耗蓝牙也是建立在传统蓝牙基础之上发展起来的,并区别于传统模块,最大的特点就是成本和功耗降低,应用于实时性要求比较高。BLE(Bluetooh Low Energy)蓝牙低能耗技术是短距离、低成本、可互操作性的无线技术,它利用许多智能手段最大限度地降低功耗。BLE技术的工作模式非常适合用于从微型无线传感器(每半秒交换一次数据)或使用完全异步通信的遥控器等其它外设传送数据。这些设备发送的数据量非常少(通常几个字节),而且发送次数也很少(例如每秒几次到每分钟一次,甚至更少)。 二、BLE协议栈的结构和配置 1、协议有两个部分组成:Controller和Host2、Profiles和应用总是基于GAP和GATT之上3、在单芯片方案中,Controller和Host,profiles,和应用层都在同一片芯片中4、在网络控制器模式中,Host和Controller是在一起运行的,但是应用和profiles在另外一个器件上,比如PC或者其他微控制器,可以通过UART,USB进行操作5、在双芯片模式中,Controller运行在一个控制器,而应用层,profiles和Host是运行在另外一个控制器上 三、BLE协议栈各层功能机制 如上图所述,要实现一个BLE应用,首先需要一个支持BLE射频的芯片,然后还需要提供一个与此芯片配套的BLE协议栈,最后在协议栈上开发自己的应用。可以看出BLE协议栈是连接芯片和应用的桥梁,是实现整个BLE应用的关键。那BLE协议栈具体包含哪些功能呢?简单来说,BLE协议栈主要用来对你的应用数据进行层层封包,以生成一个满足BLE协议的空中数据包,也就是说,把应用数据包裹在一系列的帧头(header)和帧尾(tail)中。具体来说,BLE协议栈主要由如下几部分组成: PHY层(Physical layer物理层)。PHY层用来指定BLE所用的无线频段,调制解调方式和方法等。PHY层做得好不好,直接决定整个BLE芯片的功耗,灵敏度以及selectivity等射频指标。 LL层(Link Layer链路层)。LL层是整个BLE协议栈的核心,也是BLE协议栈的难点和重点。像Nordic的BLE协议栈能同时支持20个link(连接),就是LL层的功劳。LL层要做的事情非常多,比如具体选择哪个射频通道进行通信,怎么识别空中数据包,具体在哪个时间点把数据包发送出去,怎么保证数据的完整性,ACK如何接收,如何进行重传,以及如何对链路进行管理和控制等等。LL层只负责把数据发出去或者收回来,对数据进行怎样的解析则交给上面的GAP或者ATT。 HCI(Host controller interface)。HCI是可选的,HCI主要用于2颗芯片实现BLE协议栈的场合,用来规范两者之间的通信协议和通信命令等。 GAP层(Generic access profile)。GAP是对LL层payload(有效数据包)如何进行解析的两种方式中的一种,而且是最简单的那一种。GAP简单的对LL payload进行一些规范和定义,因此GAP能实现的功能极其有限。GAP目前主要用来进行广播,扫描和发起连接等。 L2CAP层(Logic link control and adaptation protocol)。L2CAP对LL进行了一次简单封装,LL只关心传输的数据本身,L2CAP就要区分是加密通道还是普通通道,同时还要对连接间隔进行管理。 SMP(Secure manager protocol)。SMP用来管理BLE连接的加密和安全的,如何保证连接的安全性,同时不影响用户的体验,这些都是SMP要考虑的工作。 ATT(Attribute protocol)。简单来说,ATT层用来定义用户命令及命令操作的数据,比如读取某个数据或者写某个数据。BLE协议栈中,开发者接触最多的就是ATT。BLE引入了attribute概念,用来描述一条一条的数据。Attribute除了定义数据,同时定义该数据可以使用的ATT命令,因此这一层被称为ATT层。 GATT(Generic attribute profile )。GATT用来规范attribute中的数据内容,并运用group(分组)的概念对attribute进行分类管理。没有GATT,BLE协议栈也能跑,但互联互通就会出问题,也正是因为有了GATT和各种各样的应用profile,BLE摆脱了ZigBee等无线协议的兼容性困境,成了出货量最大的2.4G无线通信产品。 四、BLE蓝牙模块主要应用领域 移动扩展设备 汽车电子设备 健康医疗用品:心跳带、血压计等 定位应用:室内定位、井下定位等 近距离数据采集:无线抄表、无线遥测等 数据传输:智能家居室内控制、蓝牙调光、打印机等 五、 BLE协议栈详解 协议概述 所谓协议,即将指定的字节按照一定的顺序排列起来,以便他人使用自己的设备时,能通过该协议同其他设备进行通信。协议一特点,就是有固定的帧格式,通过该格式发送,接收者通过解读帧格式,进而得到新息内容; BLE连接过程 一般通信协议,一类通信是直接发生数据,当设备接送到数据时,直接对数据进行解析,当接受到的数据合法时,即为有效数据,该类型的通信协议,主要用在有线通信协议中,比如Modbus,Can通常采用的即为该类型的通信方式。 另一类通信协议,则需要新建立连接,当双方连接建立成功了方可通信,例如TCP、BLE;BLE协议在需要进行通信时,即需要向外发送广播信号,告诉接收者,即将和它进行通信,接受者接收到广播内容后,确认是与自己通信,于是向广播者发送一响应信息,这样当广播者和接受者都有了对方的身份信息时,即表示双方连接成功。 因此,在连接过程中,必定有相应的广播帧格式。在BLE通信过程中,假设设备A需要连其他设备假设为B,则A需要不断地发送广播信号(此过程一般有一个时间间隔,在没发送广播数据时间内,芯片处于低功耗状态),每发送一次广播包,称之为一次广播事件。 前导:是一个8比特的交替序列接入地址的第一个比特为0:01010101接入地址的第一个比特为1:10101010接入地址:广播帧为固定地址:0x8E89BED6(低字节在前) 广播报文的报头:包含4bit广播报文类型、2bit保留位、1bit发送地址类型和1bit接收地址类型。 广播报文类型: 发送地址类型:0: 公共地址1:随机地址长度:广播报文的长度域包含8个比特,有效值的范围是6~37数据: 广播者地址(6个字节)+广播数据(31个字节)校验: 3个字节,为CRC校验。广播数据: 分为有效数据和无效数据 有效数据部分:包含N个AD Structure,每个AD Structure由Length,AD Type和AD Data组成。其中:Length: AD Type和AD Data的长度。AD Type: 指示AD Data数据的含义。详见https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ 六、 BLE连接建立过程 1. BLE广播与扫描 设备B不断发送广播信号给手机(Observer),如果手机不开启扫描窗口,手机是收不到设备B的广播的,如下图所示,不仅手机要开启射频接收窗口,而且只有手机的射频接收窗口跟广播发送的发射窗口匹配成功,而且广播射频通道和手机扫描射频通道是同一个通道,手机才能收到设备B的广播信号。也就是说,如果设备B在37通道发送广播包,而手机在扫描38通道,那么即使他们俩的射频窗口匹配,两者也是无法进行通信的。由于这种匹配成功是一个概率事件,因此手机扫到设备B也是一个概率事件,也就是说,手机有时会很快扫到设备B,比如只需要一个广播事件,手机有时又会很慢才能扫到设备B,比如需要10个广播事件甚至更多。 2. 建立连接(connection establishment) 根据蓝牙spec规定,advertiser发送完一个广播包之后150us(T_IFS),advertiser必须开启一段时间的射频Rx窗口,以接收来自observer的数据包。Observer就可以在这段时间里给advertiser发送连接请求。如下图所示,手机在第三个广播事件的时候扫到了设备B,并发出了连接请求CONN_REQ(CONN_REQ又称为CONNECT_IND)。 注:图中M代表手机,S代表设备B,M->S表示手机将数据包发给设备B,即手机开启Tx窗口,设备B开启Rx窗口;S->M正好相反,表示设备B将数据包发给手机,即设备B开启Tx窗口,手机开启Rx窗口。 如图所示,手机在收到A1广播包ADV_IND后,以此为初始锚点(这个锚点不是连接的锚点),T_IFS时间后给Advertiser发送一个connection request命令,即A2数据包,告诉advertiser我将要过来连你,请做好准备。Advertiser根据connect_req命令信息做好接收准备,connect_req包含如下关键信息: Transmit window offset,定义如上图示 Transmit window size,定义如上图所示 connect_req数据包完整定义如下所示 connect_req其实是在告诉advertiser,手机将在Transmit Window期间发送第一个同步包(P1)给你,请在这段时间里把你的射频接收窗口打开。设备B收到P1后,T_IFS时间后将给手机回复数据包P2(ACK包)。 一旦手机收到数据包P2,连接即可认为建立成功。当然,实际情况会比较复杂,手机有可能收不到P2,这个时候手机将持续发送同步包直到超时时间(supervision timeout)到,在此期间只要设备B回过一次ACK包,连接即算成功。所以一旦P1包发出,主机(手机)即认为连接成功,而不管有没有收到设备的ACK包。 这也是为什么在Android或者iOS系统中,应用经常收到连接成功的回调事件(该回调事件就是基于P1包有没有发出,只要P1包发出,手机即认为连接成功,而不管有没有收到设备的ACK包),但实际上手机和设备并没有成功建立连接。 后续手机将以P1为锚点(原点),Connection Interval为周期,周期性地给设备B发送数据包(Packet),Packet除了充当数据传送功能,它还有如下两个非常重要的功能:同步手机和设备的时钟,也就是说,设备每收到手机发来的一个包,都会把自己的时序原点重新设置,以跟手机同步。 告诉设备你现在可以传数据给我了。连接成功后,BLE通信将变成主从模式,因此把连接发起者(手机)称为Master或者Central,把被连接者(之前的Advertiser)称为Slave或者Peripheral。 BLE通信之所以为主从模式,是因为Slave不能“随性”给Master发信息,它只有等到Master给它发了一个packet后,然后才能在规定的时间把自己的数据回传给Master。 3. 连接失败 有如下几种典型的连接失败情况: 如步骤2图所示,如果slave在transmit window期间没有收到master发过来的P1,那么连接将会失败。此时应该排查master那边的问题,看看master为什么没有在约定的时间把P1发出来。 如果master在transmit window期间把P1发出来了,也就是说master按照connect_req约定的时序把P1发出来了,但slave没有把P2回过去或者没有在超时时间内把P2回过去,那么连接也会失败。此时应该排查slave这边的问题,看一看slave为什么没有把P2回过去 如果master把P1发出来了,slave也把P2回过去了,此时主机或者从机还是报连接失败,这种情况有可能是软件有问题,需要仔细排查master或者slave的软件。 还有一种比较常见的连接失败情况:空中射频干扰太大。此时应该找一个干净的环境,比如屏蔽室,排除干扰后再去测试连接是否正常。 七、帧格式 1、数据链路层报文结构 报文的基础是数据链路层的报文,其它报文都是从此展开的,BLE数据链路层数据格式如下:在最新的core spec 5.2中,有1M PHY和2M的PHY,对应前导符变为1-2个字节。前导符用于频率同步、时序评估和自动增益控制训练。前导符第一bit应该与接入地址的LSB相同。字段解析: 报文类型(低 4 个 bit):ADV_IND(0000) ——通用广播ADV_DIRECT_IND(0001) ——定向连接广播ADV_NONCONN_IND(0010) ——不可连接广播ADV_SCAN_IND(0110) ——可扫描广播SCAN_REQ( 0011) ——主动扫描请求SCAN_RSP( 0100) ——主动扫描应答CONNECT_REQ( 0101) ——连接请求 发送地址( TXADD)和接收地址( RXADD): 当此位为“1”时表示 Random Add(随机地址),当此位为“0”时表示 Public Add(公共地址)。 这个地址指的是数据净荷中最初的几个地址字节。 净荷长度: 这个长度是指在 PDU 中的数据除去报头和长度之外的有效净荷数 据长度。 2、 广播通道与数据通道 PDU 区别: 广播通道的 PDU 格式:字段解析: 报文类型(低 4 个 bit):ADV_IND(0000) ——通用广播ADV_DIRECT_IND(0001) ——定向连接广播ADV_NONCONN_IND(0010) ——不可连接广播ADV_SCAN_IND(0110) ——可扫描广播SCAN_REQ( 0011) ——主动扫描请求SCAN_RSP( 0100) ——主动扫描应答CONNECT_REQ( 0101) ——连接请求 发送地址( TXADD)和接收地址( RXADD): 当此位为“1”时表示 Random Add (随机地址),当此位为“0”时表示 Public Add(公共地址)。 这个地址指的是数据净荷中最初的几个地址字节。 净荷长度: 这个长度是指在 PDU 中的数据除去报头和长度之外的有效净荷数据长度。 数据通道的 PDU 格式: 字段释义:LLID:表示此包数据是 LL Date PDU 还是 LL Control PDU 00b: Reserved01b: LL Date PDU:Continuation fragment of L2CAP message, or an Empty PDU.10b: LL Date PDU:Start of an L2CAP message or a complete L2CAP message with no fragmentation.11b: LL Control PDU MIC( Message Integrity Check):信息完整性检测。涉及到加密操作,上图中是用虚线表示的,并不是一定要有此项。MD:这个标志位是用来通知对方设备自己还有其他数据准备发送。0 表示没有更多数据发送, 1 表示有更多数据准备发送。这样,只要还有数据需要发送,连接事件会自动扩展。一旦不再有数据发送,连接事件立即关闭。 Note:如何区分是确定包、新包还是重发包?SN:只有一个 bit 位,所以值是在 0 和 1 之间进行切换。如果序列号与之前的一样,则为重传报文,如果序列号和之间的不同,则为新报文。NESN:预期序列号,它是接收方希望接到的下一包的序列号,也就是数据包的确认标志。当设备接收到序列(SN)为 0 的报文后,在发送给对方的数据包中,应将 NESN 设为 1,这样对方接收到这个包后,会发送一个新的数据包过来,否则就会重发上一次序列号为 0 的包。这个标志可以用来判断数据包是否被正确接收还是需要重传。 3、 BLE 报文格式 4、 AD Structure 解析 (1) AD type(2) AD data 简述 Flags:SERVICE: Local Name:TX Power Level: 八、代码示例 本例以OSAL下BLE代码为例做讲解。 什么是OSAL? OSAL为:Operating System Abstraction Layer,即操作系统抽象层,支持多任务运行,它并不是一个传统意义上的操作系统,但是实现了部分类似操作系统的功能。 OSAL概念是由TI公司在ZIGBEE协议栈引入,他的意思是”模拟操作系统”,此OS,并非一个真正的OS,而是模拟OS的一些方法为广大编程者提供一种写MCU程序的方法。当有一个事件发生的时候,OSAL负责将此事件分配给能够处理此事件的任务,然后此任务判断事件的类型,调用相应的事件处理程序进行处理。 实验平台 1、蓝牙协议栈:1.3.22、软件平台:IAR For 8051 8.10.33、硬件平台:Smart RF开发板(从机),Android_Lightblue(主机) 代码解析 int main(void) int main(void) { /* Initialize hardware */ HAL_BOARD_INIT();//初始化时钟和使能缓存预取模式 // Initialize board I/O InitBoard( OB_COLD );//冷启动,关闭了led灯与中断,避免接下来的各种初始化受干扰 /* Initialze the HAL driver */ HalDriverInit();//各种驱动的初始化、如按键、lcd、adc、usb、uart等8 /* Initialize NV system */ osal_snv_init();//snv 内部用于保存配对数据或你的用户自定义数据的一段flash,4kB空间 /* Initialize LL */ /* Initialize the operating system */ osal_init_system();//oasl 操作系统初始化, 包含内存分配、消息队列、定时器、电源管理和任务等 /* Enable interrupts */ HAL_ENABLE_INTERRUPTS();// 开启全局中断 // Final board initialization InitBoard( OB_READY ); //设置标志标示系统初始化完毕 } osal_init_system() uint8 osal_init_system( void ) { // Initialize the Memory Allocation System osal_mem_init();//初始化内存分配系统 // Initialize the message queue osal_qHead = NULL;//初始化消息队列 // Initialize the timers osalTimerInit();//初始化定时器 // Initialize the Power Management System osal_pwrmgr_init();//初始化电源管理系统 // Initialize the system tasks. osalInitTasks();//初始化系统任务, 这一个任务初始花非常关键 // Setup efficient search for the first free block of heap. osal_mem_kick(); return ( SUCCESS ); } osalInitTasks() void osalInitTasks( void ) { /* L2CAP Task */ L2CAP_Init( taskID++ ); /* GAP Task */ GAP_Init( taskID++ ); /* GATT Task */ GATT_Init( taskID++ ); /* SM Task */ SM_Init( taskID++ ); /* Profiles */ GAPRole_Init( taskID++ ); //链路角色初始化 GAPBondMgr_Init( taskID++ ); //链路绑定初始化 GATTServApp_Init( taskID++ ); /* Application */ SimpleBLEPeripheral_Init( taskID ); } GAPRole_Init( taskID++ ) void GAPRole_Init( uint8 task_id ) { gapRole_TaskID = task_id; //定义任务地址 gapRole_state = GAPROLE_INIT; //链路状态设置为GAPROLE_INIT gapRole_ConnectionHandle = INVALID_CONNHANDLE; //设置链路连接句柄为0xFFFF GAP_RegisterForHCIMsgs( gapRole_TaskID );//注册控制接口的任务ID // Initialize the Profile Advertising and Connection Parameters gapRole_profileRole = GAP_PROFILE_PERIPHERAL; //链路配置角色为从机 VOID osal_memset( gapRole_IRK, 0, KEYLEN ); //密钥缓冲器清零 VOID osal_memset( gapRole_SRK, 0, KEYLEN ); gapRole_signCounter = 0; //密钥计数标志位清零 gapRole_AdvEventType = GAP_ADTYPE_ADV_IND; //广播类型为可连接无定向广播 gapRole_AdvDirectType = ADDRTYPE_PUBLIC; //广播方式为通过广播(可被发现扫描连接) gapRole_AdvChanMap = GAP_ADVCHAN_ALL ; //广播所有通道37、38、39 gapRole_AdvFilterPolicy = GAP_FILTER_POLICY_ALL; //允许扫描,允许连接 // Restore Items from NV VOID osal_snv_read( BLE_NVID_IRK, KEYLEN, gapRole_IRK ); //读出存储的密钥和密钥计数标志位 VOID osal_snv_read( BLE_NVID_CSRK, KEYLEN, gapRole_SRK ); VOID osal_snv_read( BLE_NVID_SIGNCOUNTER, sizeof( uint32 ), &gapRole_signCounter ); } SimpleBLEPeripheral_Init //初始化完成后 void SimpleBLEPeripheral_Init( uint8 task_id ) { osal_set_event( simpleBLEPeripheral_TaskID, SBP_START_DEVICE_EVT ); //启动设备开始事件 } SimpleBLEPeripheral_ProcessEvent uint16 SimpleBLEPeripheral_ProcessEvent( uint8 task_id, uint16 events ) { if ( events & SBP_START_DEVICE_EVT )// 初始化后就执行这个啦 { // Start the Device VOID GAPRole_StartDevice( &simpleBLEPeripheral_PeripheralCBs ); //配置链路事件通知回调函数 // Start Bond Manager VOID GAPBondMgr_Register( &simpleBLEPeripheral_BondMgrCBs ); //配置配对消息回调函数 // Set timer for first periodic event osal_start_timerEx( simpleBLEPeripheral_TaskID, POWER_DETECT_EVT, DetectPowerPeriod ); return ( events ^ SBP_START_DEVICE_EVT ); } } GAPRole_StartDevice Status_t GAPRole_StartDevice( gapRolesCBs_t *pAppCallbacks ) { if ( gapRole_state == GAPROLE_INIT ) //如果链路状态是初始化状态 { // Clear all of the Application callbacks if ( pAppCallbacks ) { pGapRoles_AppCGs = pAppCallbacks;//设置回调函数 } // Start the GAP gapRole_SetupGAP();//开始建立链路 return ( SUCCESS ); } else //否则返回已经在请求模式状态 { return ( bleAlreadyInRequestedMode ); } } gapRole_SetupGAP static void gapRole_SetupGAP( void ) { VOID GAP_DeviceInit( gapRole_TaskID, gapRole_profileRole, 0, gapRole_IRK, gapRole_SRK, &gapRole_signCounter ); } GAP_DeviceInit bStatus_t GAP_DeviceInit( uint8 taskID, uint8 profileRole, uint8 maxScanResponses, uint8 *pIRK, uint8 *pSRK, uint32 *pSignCounter ) { // Setup the device configuration parameters stat = GAP_ParamsInit( taskID, profileRole ); //设置设备配置参数 #if ( HOST_CONFIG & ( CENTRAL_CFG | PERIPHERAL_CFG ) ) { GAP_SecParamsInit( pIRK, pSRK, pSignCounter ); } #endif #if ( HOST_CONFIG & ( PERIPHERAL_CFG | BROADCASTER_CFG ) ) { // Initialize GAP Peripheral Device Manager VOID GAP_PeriDevMgrInit(); //初始化从机设备管理 #if ( HOST_CONFIG & PERIPHERAL_CFG ) { // Initialize SM Responder VOID SM_ResponderInit(); //回应者初始化 } #endif } #endif } 当GAP_DeviceInit初始化完成后,将产生GAP_DEVICE_INIT_DONE_EVENT事件;uint16 GAPRole_ProcessEvent( uint8 task_id, uint16 events ) //链路处理事件 static void gapRole_ProcessOSALMsg( osal_event_hdr_t *pMsg ) //链路系统消息事件 gapRole_ProcessGAPMsg static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //链路处理连接消息 { uint8 notify = FALSE; // State changed notify the app? (default no) switch ( pMsg->opcode ) { case GAP_DEVICE_INIT_DONE_EVENT: //当GAP_DeviceInit初始化完成后,将产生此事件 { gapDeviceInitDoneEvent_t *pPkt = (gapDeviceInitDoneEvent_t *)pMsg; bStatus_t stat = pPkt->hdr.status; if ( stat == SUCCESS ) { // Save off the generated keys VOID osal_snv_write( BLE_NVID_IRK, KEYLEN, gapRole_IRK );//保存生成的密钥 VOID osal_snv_write( BLE_NVID_CSRK, KEYLEN, gapRole_SRK ); // Save off the information VOID osal_memcpy( gapRole_bdAddr, pPkt->devAddr, B_ADDR_LEN );//保存设备地址 gapRole_state = GAPROLE_STARTED; //链路开始 // Update the advertising data stat = GAP_UpdateAdvertisingData( gapRole_TaskID,//更新广播数据 TRUE, gapRole_AdvertDataLen, gapRole_AdvertData ); } notify = TRUE; //通知回调函数链路的状态 } break; if ( notify == TRUE ) { // Notify the application with the new state change if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange ) //判断是否设置了回调函数 { pGapRoles_AppCGs->pfnStateChange( gapRole_state );//调用设置的回调函数,通知gapRole_state当前状态 } } } stat=GAP_UpdateAdvertisingData( gapRole_TaskID,TRUE, gapRole_AdvertDataLen, gapRole_AdvertData );//更新广播数据后,将产生GAP_ADV_DATA_UPDATE_DONE_EVENT事件; gapRole_ProcessGAPMsg static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //链路处理连接消息 { uint8 notify = FALSE; // State changed notify the app? (default no) switch ( pMsg->opcode ) { case GAP_ADV_DATA_UPDATE_DONE_EVENT: { gapAdvDataUpdateEvent_t *pPkt = (gapAdvDataUpdateEvent_t *)pMsg; if ( pPkt->hdr.status == SUCCESS ) { if ( pPkt->adType ) { // Setup the Response Data pPkt->hdr.status = GAP_UpdateAdvertisingData( gapRole_TaskID, FALSE, gapRole_ScanRspDataLen, gapRole_ScanRspData );//更新扫描回应数据 } else { // Start advertising VOID osal_set_event( gapRole_TaskID, START_ADVERTISING_EVT ); //启动广播事件 } } if ( pPkt->hdr.status != SUCCESS ) //如果不成功将通知回调函数,否则不通知 { // Set into Error state gapRole_state = GAPROLE_ERROR; notify = TRUE; } } break; gapRole_ProcessGAPMsg static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //链路处理连接消息 { uint8 notify = FALSE; // State changed notify the app? (default no) switch ( pMsg->opcode ) { case GAP_ADV_DATA_UPDATE_DONE_EVENT: { gapAdvDataUpdateEvent_t *pPkt = (gapAdvDataUpdateEvent_t *)pMsg; if ( pPkt->hdr.status == SUCCESS ) { if ( pPkt->adType ) { // Setup the Response Data pPkt->hdr.status = GAP_UpdateAdvertisingData( gapRole_TaskID, FALSE, gapRole_ScanRspDataLen, gapRole_ScanRspData );//更新扫描回应数据 } else { // Start advertising VOID osal_set_event( gapRole_TaskID, START_ADVERTISING_EVT ); //启动广播事件 } } if ( pPkt->hdr.status != SUCCESS ) //如果不成功将通知回调函数,否则不通知 { // Set into Error state gapRole_state = GAPROLE_ERROR; notify = TRUE; } } break; 执行广播事件 uint16 GAPRole_ProcessEvent( uint8 task_id, uint16 events ) { VOID task_id; // OSAL required parameter that isn't used in this function if ( events & START_ADVERTISING_EVT ) { if ( gapRole_AdvEnabled ) { gapAdvertisingParams_t params; // Setup advertisement parameters params.eventType = gapRole_AdvEventType; //GAP_ADTYPE_ADV_IND; 广播类型为可连接无定向广播 params.initiatorAddrType = gapRole_AdvDirectType; //ADDRTYPE_PUBLIC; 广播方式为通用广播 VOID osal_memcpy( params.initiatorAddr, gapRole_AdvDirectAddr, B_ADDR_LEN ); //发起者地址配置 params.channelMap = gapRole_AdvChanMap; //广播通道配置:广播所有通道37、38、39 params.filterPolicy = gapRole_AdvFilterPolicy;//过滤策略GAP_FILTER_POLICY_ALL;允许扫描,允许连接 if ( GAP_MakeDiscoverable( gapRole_TaskID, ¶ms ) != SUCCESS ) //配置广播参数,并产生一个GAP_MakeDiscoverable 消息事件 { gapRole_state = GAPROLE_ERROR;//如果不成功将通知回调函数-链路错误 // Notify the application with the new state change if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange ) { pGapRoles_AppCGs->pfnStateChange( gapRole_state ); } } } return ( events ^ START_ADVERTISING_EVT ); } 处理GAP_MakeDiscoverable消息事件 static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //链路处理连接消息 { uint8 notify = FALSE; // State changed notify the app? (default no) switch ( pMsg->opcode ) { case GAP_MAKE_DISCOVERABLE_DONE_EVENT: //使能可被发现完成事件即开始广播了 case GAP_END_DISCOVERABLE_DONE_EVENT: //结束可被发现完成事件即停止广播了 { gapMakeDiscoverableRspEvent_t *pPkt = (gapMakeDiscoverableRspEvent_t *)pMsg; if ( pPkt->hdr.status == SUCCESS ) { if ( pMsg->opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT ) { gapRole_state = GAPROLE_ADVERTISING; //设置当前链路状态 } else // GAP_END_DISCOVERABLE_DONE_EVENT//结束可被发现完成事件即停止广播了 { if ( gapRole_AdvertOffTime != 0 ) //如果gapRole_AdvertOffTime等于0,将不再广播,否则启动定时广播件 { if ( ( gapRole_AdvEnabled ) )//如果使能广播 { VOID osal_start_timerEx( gapRole_TaskID, START_ADVERTISING_EVT, gapRole_AdvertOffTime );//启动周期广播事件 } } else { // Since gapRole_AdvertOffTime is set to 0, the device should not // automatically become discoverable again after a period of time. // Set enabler to FALSE; device will become discoverable again when // this value gets set to TRUE gapRole_AdvEnabled = FALSE; } // In the Advertising Off period gapRole_state = GAPROLE_WAITING;//如果GAP_END_DISCOVERABLE_DONE_EVENT,链路当前状态为等待状态 } } else { gapRole_state = GAPROLE_ERROR; } notify = TRUE;//通知回调函数 } break; if ( notify == TRUE ) { // Notify the application with the new state change if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange ) //判断是否设置了回调函数 { pGapRoles_AppCGs->pfnStateChange( gapRole_state );//调用设置的回调函数,通知gapRole_state当前状态 } } 这时候底层已经使能硬件在广播了,要么广播超时产生一个GAP_END_DISCOVERABLE_DONE_EVENT消息,要么被连接事件 GAP_LINK_ESTABLISHED_EVENT; 广播超时产生一个GAP_END_DISCOVERABLE_DONE_EVENT消息 static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //链路处理连接消息 { uint8 notify = FALSE; // State changed notify the app? (default no) switch ( pMsg->opcode ) { case GAP_MAKE_DISCOVERABLE_DONE_EVENT: //使能可被发现完成事件即开始广播了 case GAP_END_DISCOVERABLE_DONE_EVENT: //结束可被发现完成事件即停止广播了 { gapMakeDiscoverableRspEvent_t *pPkt = (gapMakeDiscoverableRspEvent_t *)pMsg; if ( pPkt->hdr.status == SUCCESS ) { if ( pMsg->opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT ) { gapRole_state = GAPROLE_ADVERTISING; //设置当前链路状态 } else // GAP_END_DISCOVERABLE_DONE_EVENT//结束可被发现完成事件即停止广播了 { if ( gapRole_AdvertOffTime != 0 ) //如果gapRole_AdvertOffTime不等于0,启动定时广播事件,否则将关闭广播 { if ( ( gapRole_AdvEnabled ) )//如果使能广播 { VOID osal_start_timerEx( gapRole_TaskID, START_ADVERTISING_EVT, gapRole_AdvertOffTime );//启动周期广播事件 } } else { // Since gapRole_AdvertOffTime is set to 0, the device should not // automatically become discoverable again after a period of time. // Set enabler to FALSE; device will become discoverable again when // this value gets set to TRUE gapRole_AdvEnabled = FALSE; //关闭广播 } // In the Advertising Off period gapRole_state = GAPROLE_WAITING;//如果GAP_END_DISCOVERABLE_DONE_EVENT,链路当前状态为等待状态,或不再广播或等待周期广播 } } else { gapRole_state = GAPROLE_ERROR; } notify = TRUE;//通知回调函数 } break; if ( notify == TRUE ) { // Notify the application with the new state change if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange ) //判断是否设置了回调函数 { pGapRoles_AppCGs->pfnStateChange( gapRole_state );//调用设置的回调函数,通知gapRole_state当前状态 } } 广播时产生一个GAP_LINK_ESTABLISHED_EVENT消息 static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //链路处理连接消息 { uint8 notify = FALSE; // State changed notify the app? (default no) switch ( pMsg->opcode ) { case GAP_LINK_ESTABLISHED_EVENT: { gapEstLinkReqEvent_t *pPkt = (gapEstLinkReqEvent_t *)pMsg; if ( pPkt->hdr.status == SUCCESS ) { VOID osal_memcpy( gapRole_ConnectedDevAddr, pPkt->devAddr, B_ADDR_LEN );//保存主机的地址 gapRole_ConnectionHandle = pPkt->connectionHandle; //保存主机连接句柄 gapRole_state = GAPROLE_CONNECTED; //通知链路状态:连接成功 notify = TRUE; } } }

优秀的个人博客,低调大师

开始写博客啦啦

mapreduce1.0架构原理: jobtraker 单点故障只支持mapreducejobtraker性能瓶颈资源利用率不高 2.yarn产生的背景降低运维成本统一资源管理,数据共享 两级调度,resourceManager 做资源统一管理处理客户端请求监控applicationmaster 具体任务执行情况监控nodemanager 节点状态 Nodemanager单节点资源管理和使用资源管理任务管理处理resourceManager的名利处理applicationMaster的命令 ApplicationMaster单个任务有一个,负责应用程序的管理数据切分为应用程序申请资源,进一步分配给内部任务任务的监控与容错 Container 对运行环境的抽象任务运行环境任务资源 yarn的容错机制 资源流向resourceManager---------->applicationMaster----------------->task yarn的资源隔离 cpu和内存两种方式 yarn的调度语义(接口),程序可以自己申请资源,自己上交资源: hadoop和yarn上的计算框架: yarn总体架构 mr的原理: storm原理: spark原理: tez原理:

优秀的个人博客,低调大师

特征工程个人笔记

特征工程 1.概述 2方法 2.1去除唯一性 2.2处理缺失值 2.3 特征编码 2.3.1特征二元化 2.3.2独热编码(One-HotEncoding) 2.3.3标签编码 2.3.4多项式特征 2.3.5交叉验证 2.3.6网格搜索 2.3.7管道机制 2.4数据标准化、正则化 2.5 特征选择(降维) 1.概述 在工程实践中,我们得到的数据会存在有缺失值、重复值等,在使用之前需要进行数据预处理。数据预处理没有标准的流程,通常针对不同的任务和数据集属性的不同而不同。数据预处理的常用流程为:去除唯一属性、处理缺失值、属性编码、数据标准化正则化、特征选择、主成分分析。 2方法 2.1去除唯一性 唯一属性通常是一些id属性,这些属性并不能刻画样本自身的分布规律,所以简单地删除这些属性即可。 2.2处理缺失值 缺失值处理的三种方法:直接使用含有缺失值的特征;删除含有缺失值的特征(该方法在包含缺失值的属性含有大量缺失值而仅仅包含极少量有效值时是有效的)→dropna();缺失值补全→fillna( )。判断是否缺失→isnull( ) 常见的缺失值补全方法:均值插补、同类均值插补、建模预测、高维映射、多重插补、极大似然估计、压缩感知和矩阵补全。 (1)均值插补 如果样本属性的距离是可度量的,则使用该属性有效值的平均值来插补缺失的值; (2)同类均值插补 首先将样本进行分类,然后以该类中样本的均值来插补缺失值。 (3)建模预测 将缺失的属性作为预测目标来预测,将数据集按照是否含有特定属性的缺失值分为两类,利用现有的机器学习算法对待预测数据集的缺失值进行预测。 该方法的根本的缺陷是如果其他属性和缺失属性无关,则预测的结果毫无意义;但是若预测结果相当准确,则说明这个缺失属性是没必要纳入数据集中的;一般的情况是介于两者之间。 (4)高维映射 将属性映射到高维空间,采用独热码编码(one-hot)技术。将包含K个离散取值范围的属性值扩展为K+1个属性值,若该属性值缺失,则扩展后的第K+1个属性值置为1。 这种做法是最精确的做法,保留了所有的信息,也未添加任何额外信息,若预处理时把所有的变量都这样处理,会大大增加数据的维度。这样做的好处是完整保留了原始数据的全部信息、不用考虑缺失值;缺点是计算量大大提升,且只有在样本量非常大的时候效果才好。 (5)多重插补(MultipleImputation,MI) 多重插补认为待插补的值是随机的,实践上通常是估计出待插补的值,再加上不同的噪声,形成多组可选插补值,根据某种选择依据,选取最合适的插补值。 (6)压缩感知和矩阵补全 (7)手动插补 插补处理只是将未知值补以我们的主观估计值,不一定完全符合客观事实。在许多情况下,根据对所在领域的理解,手动对缺失值进行插补的效果会更好。 2.3 特征编码 2.3.1特征二元化 特征二元化的过程是将数值型的属性转换为布尔值的属性,设定一个阈值作为划分属性值为0和1的分隔点。二进制转化-Binarizer 2.3.2独热编码(One-HotEncoding) from sklearn.preprocessing import OneHotEncoder 独热编码采用N位状态寄存器来对N个可能的取值进行编码,每个状态都由独立的寄存器来表示,并且在任意时刻只有其中一位有效。 独热编码的优点:能够处理非数值属性;在一定程度上扩充了特征;编码后的属性是稀疏的,存在大量的零元分量。 pandas的独热编码: pandas.get_dummies(data, prefix=None, prefix_sep=’_’, dummy_na=False, columns=None, sparse=False, drop_first=False) data : array-like, Series, or DataFrame 输入的数据 prefix : string, list of strings, or dict of strings, default None get_dummies转换后,列名的前缀 columns : list-like, default None 指定需要实现类别转换的列名 dummy_na : bool, default False 增加一列表示空缺值,如果False就忽略空缺值 drop_first : bool, default False 获得k中的k-1个类别值,去除第一个 2.3.3标签编码 from sklearn.preprocessing LabelEncoder LabelEncoder可以将标签分配一个0—n_classes-1之间的编码-不考虑数据间平等性 2.3.4多项式特征 from sklearn.preprocessing import PolynomialFeatures 简单理解多项式特征处理:就是一个数据集假如有a,b两个特征,那么对这个特征进行2次多项式特征处理的结果就是(1,ab,a2,b^2)PolynomialFeatures有三个参数: degree:控制多项式的度,也就是多项式的最高次数,int类型 interaction_only: 默认为False,如果指定为True,那么就不会有特征自己和自己结合的项,上面的二次项中没有a2和b2。 include_bias:默认为True。如果为True的话,那么就会有上面的 1那一项。 2.3.5交叉验证 from sklearn.model_selection import cross_val_score 交叉验证种类:详细参考见此贴 train_test_split:在分类问题中,我们通常通过对训练集进行train_test_split,划分成train 和test 两部分,其中train用来训练模型,test用来评估模型,模型通过fit方法从train数据集中学习,然后调用score方法在test集上进行评估,打分;从分数上我们可以知道 模型当前的训练水平如何 —from sklearn.model_selection import train_test_split X_train,X_test, y_train, y_test =train_test_split(train_data,train_target,test_size=0.4, random_state=0,stratify=y_train) train_data:所要划分的样本特征集 train_target:所要划分的样本结果 test_size:样本占比,如果是整数的话就是样本的数量 random_state:是随机数的种子。 其实就是该组随机数的编号,在需要重复试验的时候,保证得到一组一样的随机数。比如你每次都填1,其他参数一样的情况下你得到的随机数组是一样的。但填0或不填,每次都会不一样。 随机数的产生取决于种子,随机数和种子之间的关系遵从以下两个规则: 种子不同,产生不同的随机数;种子相同,即使实例不同也产生相同的随机数。也可以对X,y进行切分K-Fold Cross-validation k折交叉验证:简言之,就是进行多次train_test_split划分;每次划分时,在不同的数据集上进行训练、测试评估,从而得出一个评价结果;如果是5折交叉验证,意思就是在原始数据集上,进行5次划分,每次划分进行一次训练、评估,最后得到5次划分后的评估结果,一般在这几次评估结果上取平均得到最后的评分。k-fold cross-validation ,其中,k一般取5或10。Stratified K-Fold Cross-validation 分层k折交叉验证:解决简单交叉验证带来的问题,所以进行分层交叉验证,保证每次划分中包含类别的比例数和原数据相同。如原数据有三类,比例为1:2:1,那么划分的k折中,每一折中的数据类别保持着1:2:1的比例,这样的验证结果更加可信。 实际上就是改变了交叉验证的划分函数。Leave-one-out Cross-validation 留一法:留一法是一种特殊的交叉验证方式,顾名思义就是每次留下一个样本进行验证,如果样本容量是n 那么k折中的k等于n,进行n折交叉验证,适合小样本数据,耗时,相当于n折交叉验证,改变交叉验证数据划分函数即可。 2.3.6网格搜索 —from sklearn.model_selection import GridSearchCV Grid Search:一种调参手段;穷举搜索:在所有候选的参数选择中,通过循环遍历,尝试每一种可能性,表现最好的参数就是最终的结果。其原理就像是在数组里找最大值。(为什么叫网格搜索?以有两个参数的模型为例,参数a有3种可能,参数b有4种可能,把所有可能性列出来,可以表示成一个3*4的表格,其中每个cell就是一个网格,循环过程就像是在每个网格里遍历、搜索,所以叫grid search) 2.3.7管道机制 —from sklearn.pipeline import Pipeline 注意管道机制里面的参数写法是列表+元组的格式,列表用于存放所有的特征和算法处理的过程,元组具体写每个处理方式,每个管道机制中最多可以在最后添加一个算法,或者不填,格式如下: 2.4数据标准化、正则化 数据标准化 数据标准化是将样本的属性缩放到某个指定的范围。 数据标准化的原因: 某些算法要求样本具有零均值和单位方差; 需要消除样本不同属性具有不同量级时的影响:①数量级的差异将导致量级较大的属性占据主导地位;②数量级的差异将导致迭代收敛速度减慢;③依赖于样本距离的算法对于数据的数量级非常敏感。 min-max标准化(归一化):对于每个属性,设minA和maxA分别为属性A的最小值和最大值,将A的一个原始值x通过min-max标准化映射成在区间[0,1]中的值x’,其公式为:新数据=(原数据 - 最小值)/(最大值 - 最小值) from sklearn.preprocessing import MinMaxScaler z-score标准化(规范化):基于原始数据的均值(mean)和标准差(standarddeviation)进行数据的标准化。将A的原始值x使用z-score标准化到x’。z-score标准化方法适用于属性A的最大值和最小值未知的情况,或有超出取值范围的离群数据的情况。新数据=(原数据- 均值)/ 标准差 from sklearn.preprocessing import StandardScaler 正则化——过拟合解决方案 数据正则化是将样本的某个范数(如L1范数)缩放到到位1,正则化的过程是针对单个样本的,对于每个样本将样本缩放到单位范数。 正则化包括L0、L1,L2正则: L0正则化的值是模型参数中非零参数的个数。 L1正则化表示各个参数绝对值之和,可以将一些参数直接缩减为0 L2正则化标识各个参数的平方的和的开方值,可以将一些参数变得很小但是不为0 参数稀疏一个好处是可以简化模型,避免过拟合。因为一个模型中真正重要的参数可能并不多,如果考虑所有的参数起作用,那么可以对训练数据可以预测的很好,但是对测试数据就只能呵呵了。另一个好处是参数变少可以使整个模型获得更好的可解释性。为什么参数越小,说明模型越简单呢,这是因为越复杂的模型,越是会尝试对所有的样本进行拟合,甚至包括一些异常样本点,这就容易造成在较小的区间里预测值产生较大的波动,这种较大的波动也反映了在这个区间里的导数很大,而只有较大的参数值才能产生较大的导数。因此复杂的模型,其参数值会比较大。 稀疏的参数可以防止过拟合,因此用L0范数(非零参数的个数)来做正则化项是可以防止过拟合的。 从直观上看,利用非零参数的个数,可以很好的来选择特征,实现特征稀疏的效果,具体操作时选择参数非零的特征即可。但因为L0正则化很难求解,是个NP难问题,因此一般采用L1正则化。L1正则化是L0正则化的最优凸近似,比L0容易求解,并且也可以实现稀疏的效果。 L1正则化-lasso(套索)回归 L2正则—Ridge(岭回归) 2.5 特征选择(降维) 从给定的特征集合中选出相关特征子集的过程称为特征选择。 进行特征选择的两个主要原因是: 减轻维数灾难问题; 降低学习任务的难度。 进行特征选择必须确保不丢失重要特征。 常见的特征选择类型分为三类:过滤式(filter)、包裹式(wrapper)、嵌入式(embedding)。 过滤式选择:该方法先对数据集进行特征选择,然后再训练学习器。特征选择过程与后续学习器无关。Relief是一种著名的过滤式特征选择方法。 包裹式选择:该方法直接把最终将要使用的学习器的性能作为特征子集的评价原则。其优点是直接针对特定学习器进行优化,因此通常包裹式特征选择比过滤式特征选择更好,缺点是由于特征选择过程需要多次训练学习器,故计算开销要比过滤式特征选择要大得多。 嵌入式选择 常见的降维方法:SVD、PCA、LDA

优秀的个人博客,低调大师

IntelliJ IDEA个人配置

主题 字体 推荐DejaVu Sans Mono,这个字体绝对爽翻 第二推荐Courier 或者 Courier New IDEA应用字体 代码字体 预览 常用插件 Alibaba Java Coding Guidelines阿里巴巴编程规范,可以检查自己的java命名规范编程规范以及bug,并会给出相应的解决方案 Grep Console对控制台输出的不同级别的日志进行上色,比如info级别是黑色,WARN级别是黄色,ERROR是橙色。不用找日志把眼睛找瞎了。 Kotlin项目中使用到了Kotlin可以安装 Lombok Plugin简化重复的java代码 Translation翻译

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册