
0 前言( ~2 y! A( c S! e 自己的主要目的是学习BLE相关的知识,以及其具体的应用。因此不会像前一小节那样,组成那么长链路的系统。为最佳的学习BLE,就需要组成最小的闭环回路。9 P, ` D% Q) y . a: C$ w: ]# I5 {5 o' z, Y3 F 1 最小闭环回路 最小闭环系统,就是STM32WB的开发板和笔记本组成的系统。现在基本的笔记本自带蓝牙。! A/ Z" Y( o5 ~7 P 1 ![]() 4 D9 I, E5 D" ^. \ H8 N- Z 2 系统软件环境 在开发板中的软件使用的是:P-NUCLEO-WB55.Nucleo\Applications\BLE\BLE_HeartRate项目工程。+ H1 C' w" r2 r9 a4 L 笔记本使用上位机是自己编写的python代码。 2 ![]() 我们将开发板当作外围设备。将笔记本作为中心设备。 9 U7 U* ~) |; [1 u" k 3 连接实验 3.1 发现设备( [6 H8 S) o" q' B1 { 下载好单片机程序,给单片机上电。 W' F( D: G5 h. {, ` 在笔记本设置中打开蓝牙,并点击添加蓝牙。 3 ![]() 2 ~0 H/ g; e' \9 R" ]+ n) E/ [ 可以看到我们的设备HRSTM。1 p! J( b- l" c! x+ n 接下来使用我编写好代码:/ O5 @' o N+ V) } import bleak.backends.dotnet.discovery as Discovery import asyncio4 T7 ?9 N* G/ U+ B async def run(): V( C0 K) g; [+ L7 d devices = await Discovery.discover()- H& H6 n( I# Y" d6 H2 `6 h for d in devices: print(d) loop = asyncio.get_event_loop() loop.run_until_complete(run()) 在输出窗口可以看到可以被发现了的蓝牙: B8:7C:6F:47:81:BF: MiKettle 6B:96:A5:AF:06:A7: Apple, Inc. (b'\x10\x05Q\x1c\x82.\xec')+ v" L8 h5 G: M: N( {0 _3 U 80:E1:26:00:68:7C: HRSTM * \) Z; a4 `2 l8 E/ p 我们也可以看到我们的蓝牙应用HRSTM。其中,一个很重要的东西就是设备的ID,这个是我们连接的一个关键,就和电话号码一样,而HRSTM就是持有人的别名。 3.2 连接和发现服务 接下来,我们应用上面的地址,来连接和发现一下HRSTM有哪些服务:) }! M3 q' {3 v. M4 ^5 z async with BleakClient(address, loop=loop) as client:5 O% g# n1 U7 @# L! L x = await client.is_connected()* }7 F% X4 p+ \. i6 f1 u; g log.info("Connected: {0}".format(x)) log.info("[Service] {0}: {1}".format(service.uuid, service.description)) for char in service.characteristics: if "read" in char.properties:! G. g& O2 E* f; B2 z% r try: value = bytes(await client.read_gatt_char(char.uuid)) except Exception as e:" x) Y8 V& Y/ c& I0 G5 q8 a value = str(e).encode() else: value = None) B+ V/ T$ J7 c( H/ ^ log.info($ j. q8 x8 m. p. n "\t[Characteristic] {0}: ({1}) | Name: {2}, Value: {3} ".format( char.uuid, ",".join(char.properties), char.description, value )% U- q8 E- y5 J, m' E- p )/ ^0 Y% b u" m& w# D( R" A for descriptor in char.descriptors: value = await client.read_gatt_descriptor(descriptor.handle) z$ o, J: h( _8 A& R+ H; W. x log.info(; S5 p& D; v3 m7 \& i "\t\t[Descriptor] {0}: (Handle: {1}) | Value: {2} ".format( descriptor.uuid, descriptor.handle, bytes(value) ); L: a; g: v* f1 @( o3 K a )5 s) {, z; M- U4 J8 X 输出的结果: Z( [. \. Q% y V/ j/ o! p( e Connected: True5 J% x" v% z$ C [Service] 00001801-0000-1000-8000-00805f9b34fb: Generic Attribute Profile1 Z( Y$ Z6 l) _% |" [: p& s [Characteristic] 00002a05-0000-1000-8000-00805f9b34fb: (indicate) | Name: , Value: None [Descriptor] 00002902-0000-1000-8000-00805f9b34fb: (Handle: 4) | Value: b'\x02\x00' [Service] 00001800-0000-1000-8000-00805f9b34fb: Generic Access Profile4 N J6 a$ M6 O* k [Characteristic] 00002a00-0000-1000-8000-00805f9b34fb: (read,write-without-response,write,authenticated-signed-writes) | Name: , Value: b'STM32WB'8 B% }; a3 i/ Y2 n [Characteristic] 00002a01-0000-1000-8000-00805f9b34fb: (read,write-without-response,write,authenticated-signed-writes) | Name: , Value: b'@\x03'( u- r$ f) n8 b; b1 Z+ L3 r [Characteristic] 00002a04-0000-1000-8000-00805f9b34fb: (read) | Name: , Value: b'\xff\xff\xff\xff\x00\x00\xff\xff'' N1 I0 b+ Y$ m I [Service] 0000180a-0000-1000-8000-00805f9b34fb: Device Information# P; n' \0 ~1 ?' [9 Q) S [Characteristic] 00002a29-0000-1000-8000-00805f9b34fb: (read) | Name: , Value: b'STM\x00' [Service] 0000180d-0000-1000-8000-00805f9b34fb: Heart Rate. ^* i4 S- I7 u7 s [Characteristic] 00002a37-0000-1000-8000-00805f9b34fb: (notify) | Name: , Value: None [Descriptor] 00002902-0000-1000-8000-00805f9b34fb: (Handle: 18) | Value: b'\x00\x00' [Characteristic] 00002a38-0000-1000-8000-00805f9b34fb: (read) | Name: , Value: b'\x04' [Characteristic] 00002a39-0000-1000-8000-00805f9b34fb: (write) | Name: , Value: None$ D& k4 t: l1 E5 j7 Z 观察,返回的信息,我们大致可以通过,服务、特性、描述等知道其性能和功能。其中,发现UUID都是单片机中定义好的,在UUID.h 文件目录下: /* UUIDs for Heart Rate Service */ #define HEART_RATE_SERVICE_UUID (0x180D) #define CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID (0x2902) #define HEART_RATE_MEASURMENT_UUID (0x2A37) #define SENSOR_LOCATION_UUID (0x2A38) #define CONTROL_POINT_UUID (0x2A39): I+ W: X( z. b. |3 E 3.3 通知和获取数据, F p5 [( V' F8 n. l* i% m$ A 通过STM的蓝牙软件,发现软件是有数据传递的。 4 ![]() ' B1 J$ C; I* z 其采用的方式是通知的方式,我是用了一下程序:0 h" K1 u( ?4 ^, _+ ]) A def notification_handler(sender, data): # strs=data.hex print(f"{sender}: {data}") byarray = bytearray(data) print(byarray.hex()) / p9 i" ]; j. [" |, t9 T% q async def run(address, loop, debug=False):( h( u g7 s( Y% o8 h6 ? async with BleakClient(address, loop=loop) as client: x = await client.is_connected() await client.start_notify(CHARACTERISTIC_UUID, notification_handler)( H$ O' z/ w9 w* q: J await asyncio.sleep(5.0, loop=loop) await client.stop_notify(CHARACTERISTIC_UUID)3 x& c1 F0 \5 Q. j if __name__ == "__main__": if __name__ == "__main__":7 i4 e: z7 `& z" ?. v address = ('80:E1:26:00:68:7C') #你的地址 loop = asyncio.get_event_loop() loop.run_until_complete(run(address, loop, True))+ L T i! n$ E4 l : _" ^5 U" ?- Z8 _- w6 b. } 返回了一下数据:* b. i! b; \' P) H 00002a37-0000-1000-8000-00805f9b34fb: bytearray(b'\x1f7\x00\x14\x00\x00\x04') 1f370014000004 00002a37-0000-1000-8000-00805f9b34fb: bytearray(b'\x1f6\x00\x1e\x00\x00\x04') 1f36001e000004 00002a37-0000-1000-8000-00805f9b34fb: bytearray(b'\x1f4\x00(\x00\x00\x04'); G5 z) @7 G( T 1f340028000004 6 \# t" [; d( n; n. x% Z6 ?) R* V 其中00002a37是HEART_RATE_MEASURMENT_UUID ,数据是十六进制的1f36001e000004,其中0x36是心跳,1e是能量消耗,我将能量变化修改为,每次+10,从每次读回的值就可以看到。 HRSAPP_Context.MeasurementvalueChar.EnergyExpended += 10;; v' ^5 r8 u3 \: [5 y0 Q# E# P $ Y0 L9 h: P4 ~9 L* \, y 4 小结& C. j6 n* p% w/ ~ 以上步骤,从发现蓝牙,连接蓝牙,获取蓝牙服务,以及获取数据。其实,实验中还有许多的小问题,比如指示灯的闪烁,为啥STM官方的APP可以直接连接,自己的却不行? 我相信,在后面一步一步的代码解析,就会解释这些现象。 + S4 Z6 S4 F) S5 V % ~! s M+ J# H: C, A9 i7 O |
【NUCLEO-WB09KE评测】+3.蓝牙广播
Nucleo-STM32WB09KE测评-蓝牙调试并使用OLED显示数据
【NUCLEO-WB09KE评测】创建BLE工程和开启调试信息
【NUCLEO-WB09KE评测】+2.串口打印
【NUCLEO-WB09KE评测】+5.实现标准电量服务
【NUCLEO-WB09KE评测】BLE例程HeartRate及工具探索
STM32WB系列MCU低功耗测试
Nucleo-STM32WB09KE测评--IIC传感器读取
【NUCLEO-WB09KE评测】+4.蓝牙控制LED
【NUCLEO-WB09KE评测】+1.点灯