前言
树莓派 CM0 有带无线功能与不带无线功能两个版本,具体可以参考如下型号说明:
| 型号 | 前缀 | 无线 | RAM LPDDR2 | eMMC 存储 |
|---|---|---|---|---|
| CM0000000 | CM0 | 0 = No | 00 = 512MB | 000 = 0GB (Lite) |
| CM0000008 | CM0 |
树莓派 CM0 Python BLE 设备通信指南介绍了如何在树莓派 CM0 上配置蓝牙环境并使用 Python 进行 BLE 通信。内容涵盖确认硬件无线版本、启用蓝牙电源、解除射频锁定及安装 bleak 库。教程演示了扫描附近 BLE 设备、通过 MAC 地址或名称连接、查看服务与特征列表,并实现了特征值的主动读取、订阅通知及数据写入操作,为嵌入式蓝牙开发提供完整流程参考。
树莓派 CM0 有带无线功能与不带无线功能两个版本,具体可以参考如下型号说明:
| 型号 | 前缀 | 无线 | RAM LPDDR2 | eMMC 存储 |
|---|---|---|---|---|
| CM0000000 | CM0 | 0 = No | 00 = 512MB | 000 = 0GB (Lite) |
| CM0000008 | CM0 |
| 0 = No |
| 00 = 512MB |
| 008 = 8GB |
| CM0000016 | CM0 | 0 = No | 00 = 512MB | 016 = 16GB |
| CM0100000 | CM0 | 1 = Yes | 00 = 512MB | 000 = 0GB (Lite) |
| CM0100008 | CM0 | 1 = Yes | 00 = 512MB | 008 = 8GB |
| CM0100016 | CM0 | 1 = Yes | 00 = 512MB | 016 = 16GB |
或者查看你的板子中是否带有无线模块。

如果你的 CM0 具备无线功能,则可以继续按照本教程的后续步骤进行测试。
在 BLE 通信中,设备工作于主从模式:
用一个简单的比喻,这就像美食街里有许多商贩在叫卖(从设备广播),而顾客(主设备)听到感兴趣的吆喝后,可以主动走过去交易。商贩只能等待顾客上门,而不能主动将商品塞给顾客。
在本次实验中,树莓派 CM0 将会作为主设备,对从设备进行操作。
实际上,两片树莓派 CM0 之间也可设置为一主一从进行通信,但受限于我只有一块 CM0,本次实验暂不演示该场景。
下一步安排:为便于理解与实践,下一章我们将以常见的小米温湿度计作为从设备,完成从发现,连接到数据读取的全过程演示。
输入
bluetoothctl进入蓝牙管理交互界面
root@rpi-cm0:~# bluetoothctl hci0 new_settings: bondable ssp br/edr le secure-conn Agent registered [CHG] Controller AA:CC:DD:11:22:33 Pairable: yes [bluetoothctl]>
开启蓝牙射频电源
在 bluetoothctl 交互界面中,输入 power on 以开启蓝牙射频电源
[bluetoothctl]> power on [CHG] Controller AA:CC:DD:11:22:33 PowerState: off-enabling hci0 class of device changed: 0x400000 [CHG] Controller AA:CC:DD:11:22:33 Class: 0x00400000 (4194304) hci0 new_settings: powered bondable ssp br/edr le secure-conn Changing power on succeeded [CHG] Controller AA:CC:DD:11:22:33 PowerState: on [CHG] Controller AA:CC:DD:11:22:33 Powered: yes [bluetoothctl]>
完成后,输入 exit 退出
[bluetoothctl]> exit root@rpi-cm0:~#
查看当前状态
输入命令 rfkill list 查看状态,观察 Bluetooth 项。
若 Soft blocked 为 yes 则需要解锁
root@rpi-cm0:~# rfkill list 0: hci0: Bluetooth Soft blocked: yes # 若为 yes, 表示被软锁定 Hard blocked: no 1: phy0: Wireless LAN Soft blocked: no Hard blocked: no
解除射频锁定
命令 rfkill unblock bluetooth, 无错误输出则为解锁成功。
root@rpi-cm0:~# rfkill unblock bluetooth
确认解锁状态
再次运行 rfkill list 确认 Soft blocked 已变为 no
root@rpi-cm0:~# rfkill list 0: hci0: Bluetooth Soft blocked: no # 若为 no, 表示已解锁 Hard blocked: no 1: phy0: Wireless LAN Soft blocked: no Hard blocked: no
本教程使用
bleak库进行蓝牙开发。为避免包冲突,建议通过系统包管理器安装
root@rpi-cm0:~# apt update root@rpi-cm0:~# apt install python3-bleak
代码
以下脚本将扫描并列出附近所有 BLE 设备及其信号强度 (RSSI)
import asyncio
from bleak import BleakScanner
async def main():
print("正在扫描附近的 BLE 设备 (持续 5 秒)...")
found_devices = await BleakScanner.discover(timeout=5.0, return_adv=True)
print(f"{'MAC 地址':<20} {'设备名称':<20} {'RSSI'}")
print("-" * 50)
for device, advertisement_data in found_devices.values():
# 获取设备名称
name = device.name if device.name else "Unknown"
# 获取 RSSI
rssi = advertisement_data.rssi
print(f"{device.address:<20} {name:<20} {rssi} dBm")
if __name__ == "__main__":
asyncio.run(main())
部分要点解析
方式一:通过 MAC 地址指定设备 (推荐,大多数情况下 MAC 地址唯一)
import asyncio
from bleak import BleakScanner, BleakClient
# 目标设备的 MAC 地址
TARGET_ADDRESS = "AA:CC:22:66:55:88"
async def main():
print("开始扫描设备...")
device = await BleakScanner.find_device_by_filter(
lambda d, ad: d.address and d.address == TARGET_ADDRESS
)
if not device:
print(f"未找到 MAC 地址为 {TARGET_ADDRESS} 的设备")
return
print(f"找到设备:{device.address}, 正在连接...")
async with BleakClient(device) as client:
print(f"已连接:{client.is_connected}")
print("\n--- 服务发现开始 ---")
for service in client.services:
print(f"[服务] UUID: {service.uuid}")
for char in service.characteristics:
print(f" └── [特征] UUID: {char.uuid}")
print(f" 属性:{char.properties}")
print("--- 服务发现结束 ---\n")
if __name__ == "__main__":
asyncio.run(main())
方式二:通过设备名称指定设备
import asyncio
from bleak import BleakScanner, BleakClient
# 目标设备的名称
TARGET_NAME = "LYWSD03MMC"
async def main():
print("开始扫描设备...")
device = await BleakScanner.find_device_by_filter(
lambda d, ad: d.name and d.name == TARGET_NAME
)
if not device:
print(f"未找到名为 {TARGET_NAME} 的设备")
return
print(f"找到设备:{device.address}, 正在连接...")
async with BleakClient(device) as client:
print(f"已连接:{client.is_connected}")
print("\n--- 服务发现开始 ---")
for service in client.services:
print(f"[服务] UUID: {service.uuid}")
for char in service.characteristics:
print(f" └── [特征] UUID: {char.uuid}")
print(f" 属性:{char.properties}")
print("--- 服务发现结束 ---\n")
if __name__ == "__main__":
asyncio.run(main())
部分要点解析
BleakScanner.find_device_by_filter 方法来扫描和连接 BLE 设备,区别在于筛选条件不同。连接设备后,可通过 主动读取 (Read) 或 订阅通知 (Notify) 两种方式获取特征值中的数据。
若特征仅支持读取,请注释掉通知订阅部分代码。
代码
import asyncio
import struct
from bleak import BleakClient
# ================= 配置区域 =================
# 1. 设备 MAC 地址
DEVICE_ADDRESS = "AA:CC:22:66:55:88"
# 2. 待读取的数据特征 UUID
SENSOR_CHAR_UUID = "8edfffef-3d1b-9c37-4623-ad7265f14076"
# ===========================================
# 处理实时通知的回调函数
def notification_handler(sender, data: bytearray):
"""
当传感器主动推送数据时,会触发这个函数
sender: 发送者句柄
data: 接收到的原始字节数据
"""
print(f"[通知] 收到数据 (Hex): {data.hex()}")
async def main():
print(f"正在连接设备 {DEVICE_ADDRESS} ...")
try:
async with BleakClient(DEVICE_ADDRESS) as client:
if client.is_connected:
print(f"成功连接到:{DEVICE_ADDRESS}")
# --- 方式 A: 主动读取一次 (Read) ---
try:
print("正在尝试主动读取...")
val = await client.read_gatt_char(SENSOR_CHAR_UUID)
print(f"[读取] 原始数据:{val.hex()}")
except Exception as e:
print(f"[读取] 失败 (可能该特征不支持读取): {e}")
# --- 方式 B: 开启实时通知 (Notify) ---
print("正在开启实时通知 (监听 10 秒)...")
# 开启通知,绑定回调函数
await client.start_notify(SENSOR_CHAR_UUID, notification_handler)
# 让程序保持运行 10 秒,给传感器发送数据的时间
await asyncio.sleep(10)
# 关闭通知
await client.stop_notify(SENSOR_CHAR_UUID)
print("通知已关闭")
except Exception as e:
print(f"连接或操作发生错误:{e}")
if __name__ == "__main__":
asyncio.run(main())
部分要点解析
列出指定 BLE 设备服务与特征列表 提供的代码来查看。代码
尝试写入数据 Hello Raspberry Pi CM0! 到指定设备。
import asyncio
from bleak import BleakScanner, BleakClient
# ================= 配置区域 =================
# 1. 设备 MAC 地址
TARGET_ADDRESS = "AA:CC:22:66:55:88"
# 2. 待写入的数据特征 UUID
CHARACTERISTIC_UUID = "0000fff2-0000-1000-8000-00805f9b34fb"
# ===========================================
async def main():
print("开始扫描设备...")
device = await BleakScanner.find_device_by_filter(
lambda d, ad: d.address and d.address == TARGET_ADDRESS
)
if not device:
print(f"未找到 MAC 地址为 {TARGET_ADDRESS} 的设备")
return
print(f"找到设备:{device.address}, 正在连接...")
async with BleakClient(device) as client:
print(f"已连接:{client.is_connected}")
# 尝试写入数据
try:
data_to_send = bytes("Hello Raspberry Pi CM0!", 'utf-8')
print(f"尝试写入 UUID: {CHARACTERISTIC_UUID}")
await client.write_gatt_char(CHARACTERISTIC_UUID, data_to_send)
print("写入成功!")
except Exception as e:
print(f"写入失败:{e}")
if __name__ == "__main__":
asyncio.run(main())
效果


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online