Ubuntu上基于QEMU与Zephyr构建嵌入式蓝牙Polling模式开发环境

发布时间:2026/5/16 15:07:41

Ubuntu上基于QEMU与Zephyr构建嵌入式蓝牙Polling模式开发环境 1. 项目概述在Ubuntu上构建嵌入式蓝牙开发环境最近在折腾一个物联网边缘设备的小项目需要验证一个基于Zephyr RTOS的蓝牙低功耗BLE应用。手头没有现成的开发板但又想快速验证协议栈和应用逻辑。这时候虚拟机里的模拟环境就成了最佳选择。我选择了在Ubuntu系统上使用QEMU这个强大的开源机器模拟器来运行Zephyr并重点测试其polling模式的蓝牙驱动。这听起来有点绕但本质上我们是在一台电脑里用软件模拟出一个“虚拟的”嵌入式设备然后在这个虚拟设备上运行一个轻量级的实时操作系统最后在这个系统里跑起蓝牙协议栈。整个过程绕开了硬件依赖对于协议开发、应用逻辑验证和教学演示来说效率极高。这个方案特别适合几类朋友一是嵌入式软件工程师想在硬件到位前提前开发二是物联网应用开发者想专注于上层应用而不想深陷硬件调试三是学生或研究者需要低成本、可复现的环境来学习Zephyr和蓝牙协议栈。整个环境搭建的核心就是理清Zephyr、QEMU和蓝牙虚拟化这三者之间的关系。我会带你一步步走通从环境准备、源码获取、编译配置到最终在QEMU中运行起一个可被真实手机扫描到的蓝牙设备并分享其中我踩过的坑和总结的技巧。2. 环境准备与工具链解析2.1 为什么选择Ubuntu QEMU Zephyr的组合在开始动手之前我们先聊聊为什么是这三个组件。Ubuntu作为一个成熟的Linux发行版拥有最广泛的社区支持和最完善的包管理能让我们免去很多底层依赖的烦恼。QEMU则是一个“硬件模拟器”它不仅能模拟整个计算机系统CPU、内存、外设更重要的是它支持模拟多种ARM架构的芯片而这正是Zephyr的主战场。Zephyr RTOS是Linux基金会旗下的开源实时操作系统专为资源受限的物联网设备设计其蓝牙协议栈完整且开源是学习蓝牙内部机制的绝佳平台。而polling模式是Zephyr蓝牙驱动的一种工作方式。简单类比就像你不停地检查邮箱polling和定时去查看邮箱中断的区别。Polling模式下CPU会主动、周期性地去查询蓝牙控制器是否有数据到来这种方式实现简单在模拟环境中更稳定避免了虚拟中断可能带来的时序复杂性。所以我们的目标就是在QEMU模拟的“虚拟开发板”上让Zephyr以polling模式驱动一个虚拟的蓝牙控制器。2.2 基础系统与依赖安装首先确保你使用的Ubuntu版本是20.04 LTS或22.04 LTS这两个版本经过长期测试兼容性最好。打开终端我们进行第一步更新系统并安装核心依赖。sudo apt update sudo apt upgrade -y接下来安装编译Zephyr所需的工具链和QEMU。这里有个关键点Zephyr官方推荐使用其SDKSoftware Development Kit里面包含了优化过的交叉编译工具链。但我们第一步可以先安装系统自带的工具和QEMU用于搭建基础环境。sudo apt install -y --no-install-recommends \ git cmake ninja-build gperf \ ccache dfu-util device-tree-compiler \ wget python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \ make gcc gcc-multilib g-multilib libsdl2-dev libglib2.0-dev注意--no-install-recommends参数可以避免安装非必要的推荐包让系统更干净。libsdl2-dev是QEMU图形界面如果需要的话的依赖libglib2.0-dev则是QEMU核心库依赖。然后安装QEMU。虽然我们可以从源码编译但为了简便直接安装Ubuntu仓库中的版本即可。我们需要的是支持ARM架构的QEMU系统模拟器。sudo apt install -y qemu-system-arm qemu-utils安装完成后可以通过qemu-system-arm --version命令验证。接下来是Python环境Zephyr的构建系统west是一个基于Python的元工具。pip3 install --user -U pip pip3 install --user west确保~/.local/bin在你的PATH环境变量中。可以编辑~/.bashrc文件添加export PATH~/.local/bin:$PATH然后执行source ~/.bashrc。2.3 获取Zephyr源码并安装SDKZephyr的代码管理使用west。首先拉取主仓库manifest repowest init ~/zephyrproject cd ~/zephyrproject west update这个过程会下载Zephyr操作系统本身以及所有相关的模块modules包括蓝牙协议栈位于modules/蓝牙/host和modules/蓝牙/controller等目录。下载时间取决于网络。接下来安装Zephyr SDK。这是至关重要的一步SDK提供了为ARM等架构交叉编译的优化工具链。前往Zephyr SDK的GitHub发布页面找到最新版本例如0.16.5的安装脚本下载并执行。cd /tmp wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.5/zephyr-sdk-0.16.5_linux-x86_64.tar.xz wget -O - https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.5/sha256.sum | shasum --check --ignore-missing验证哈希值无误后解压并安装tar xvf zephyr-sdk-0.16.5_linux-x86_64.tar.xz cd zephyr-sdk-0.16.5 ./setup.sh -t all安装过程中会询问安装路径默认即可。-t all参数会安装所有架构的工具链。最后设置Zephyr的环境变量echo source ~/zephyrproject/zephyr/zephyr-env.sh ~/.bashrc source ~/.bashrc执行source ~/zephyrproject/zephyr/zephyr-env.sh会设置一系列环境变量如ZEPHYR_BASE并激活Python虚拟环境如果使用。至此基础环境就绪。3. 构建支持蓝牙Polling的QEMU目标3.1 Zephyr中QEMU目标的分类与选择Zephyr支持多种QEMU模拟的板卡Board每种板卡对应一种虚拟的硬件配置。对于ARM架构常见的QEMU目标有qemu_cortex_m3: 模拟ARM Cortex-M3内核例如STM32F1系列。qemu_cortex_a53: 模拟ARM Cortex-A53内核这是一个应用处理器内核。但是并不是所有QEMU目标都默认支持蓝牙。Zephyr中蓝牙功能的支持依赖于对应的“板级支持包”Board Support Package, BSP是否配置了蓝牙驱动以及QEMU本身是否模拟了相应的蓝牙硬件。经过查阅源码和实践qemu_cortex_m3目标不支持蓝牙。而qemu_cortex_a53目标可以支持但需要正确的配置。我们通常使用qemu_cortex_a53这个目标来运行带有蓝牙的Zephyr应用。那么polling模式在哪里配置呢它不是一个板级配置而是一个驱动层面的配置选项在构建应用程序时通过Kconfig系统来开启。3.2 配置构建系统以启用蓝牙Polling驱动Zephyr使用Kconfig和Devicetree来管理系统配置。我们需要构建一个示例程序来测试蓝牙。Zephyr提供了丰富的蓝牙示例位于~/zephyrproject/zephyr/samples/蓝牙目录下。我们选择一个最简单的peripheral_hr心率计外设作为测试。首先进入示例目录并创建一个独立的构建目录out-of-tree buildcd ~/zephyrproject/zephyr/samples/蓝牙/peripheral_hr mkdir build cd build关键步骤来了使用cmake配置项目并显式指定polling模式。在Zephyr的蓝牙HCI驱动中polling模式对应的Kconfig选项是CONFIG_BT_WAIT_NOP。但实际上更直接的方式是通过CONFIG_BT_DRIVER相关的选项。对于QEMU我们使用native-posix或hci模拟驱动时通常采用CONFIG_BT_RECV_IS_RX_THREADn和CONFIG_BT_RECV_BLOCKINGy的组合来近似实现polling行为或者使用专门的CONFIG_BT_HCI_RAW和CONFIG_BT_HCI_RAW_RESERVE。然而经过对Zephyr源码drivers/蓝牙/hci的分析我发现最清晰的方法是使用**hci_usb驱动模拟**并将其设置为polling模式。但QEMU并不直接模拟USB蓝牙适配器。实际上Zephyr为QEMU ARM (qemu_cortex_a53) 提供了一个虚拟的UART作为HCI传输层驱动是hci_uart。我们可以将这个UART驱动配置为polling模式。配置命令如下cmake -GNinja -DBOARDqemu_cortex_a53 ..这行命令指定了目标板为qemu_cortex_a53。接下来我们需要修改配置。有两种方式交互式菜单配置运行ninja menuconfig。直接传递CMake变量更适用于脚本化。我们采用第二种在cmake命令中直接定义关键配置变量cmake -GNinja -DBOARDqemu_cortex_a53 \ -DCONFIG_BTy \ -DCONFIG_BT_HCI_RAWy \ -DCONFIG_BT_HCI_RAW_RESERVE1 \ -DCONFIG_BT_RECV_BLOCKINGy \ -DCONFIG_BT_RECV_IS_RX_THREADn \ -DCONFIG_UART_INTERRUPT_DRIVENn ..让我们拆解这些配置CONFIG_BTy: 总开关启用蓝牙功能。CONFIG_BT_HCI_RAWy和CONFIG_BT_HCI_RAW_RESERVE1: 启用“原始HCI”模式。这允许Zephyr应用程序直接通过UART与虚拟的蓝牙控制器通信绕过了操作系统内部的一些缓冲机制使得polling模式更易实现。RESERVE1表示使用UART设备1在QEMU中通常对应第二个串口第一个串口用于控制台。CONFIG_BT_RECV_BLOCKINGy: 这是关键。设置接收为阻塞模式。当这个选项启用时HCI驱动在等待接收数据时不会让出CPU而是忙等待busy-wait这正是polling行为的核心。CONFIG_BT_RECV_IS_RX_THREADn: 禁用独立的接收线程。与阻塞模式配合确保接收操作在应用线程上下文中以polling方式进行。CONFIG_UART_INTERRUPT_DRIVENn: 禁用UART的中断驱动。强制UART驱动使用轮询方式收发数据从底层传输层配合实现全链路的polling。实操心得一开始我试图在menuconfig里一个个找这些选项效率很低。后来发现直接阅读Kconfig文件位于subsys/蓝牙/host/Kconfig和drivers/蓝牙/Kconfig是最高效的。理解每个选项的含义才能精准组合出我们需要的polling配置。3.3 编译与生成镜像配置完成后使用ninja进行编译ninja如果一切顺利你会在build目录下看到生成的zephyr.elf、zephyr.bin和zephyr.zephyr文件。其中zephyr.elf是带有调试信息的可执行文件zephyr.bin是纯二进制镜像而zephyr.zephyr是Zephyr格式的镜像QEMU可以直接加载它。编译过程可能会遇到一些依赖问题例如某个Python模块缺失。通常根据错误提示使用pip3 install安装即可。一个常见的坑是wheel包构建失败确保你已经安装了python3-dev和构建工具。4. 运行QEMU并连接虚拟蓝牙4.1 启动QEMU模拟器编译成功后我们可以使用west命令来一键启动QEMU并运行我们的镜像这比手动输入一长串QEMU参数方便得多west build -t run或者在构建目录下直接使用ninja runninja run这个命令会调用一个预设的脚本启动QEMU加载zephyr.zephyr镜像并将串口输出重定向到当前终端。你应该能看到Zephyr的启动日志类似于*** Booting Zephyr OS build v3.4.0 *** [00:00:00.000] inf bt_hci_core: HW Platform: Nordic Semiconductor (0x0002) [00:00:00.000] inf bt_hci_core: HW Variant: nRF52x (0x0002) [00:00:00.000] inf bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 3.4 Build 99 [00:00:00.000] inf bt_hci_core: Identity: xx:xx:xx:xx:xx:xx (random) [00:xx:xx.xxx] inf peripheral_hr: Bluetooth initialized [00:xx:xx.xxx] inf peripheral_hr: Starting Advertising...注意看虽然我们模拟的是Cortex-A53但蓝牙控制器的标识可能显示为Nordic nRF52x这是因为Zephyr在QEMU中使用了一个软件模拟的蓝牙控制器其行为仿照了流行的Nordic芯片。这完全正常。4.2 关键一步在Host主机上创建虚拟蓝牙接口此时Zephyr已经在QEMU内部运行起来了并初始化了一个虚拟的蓝牙控制器。但是这个控制器“困在”QEMU的虚拟机器里。如何让宿主机的蓝牙栈例如BlueZ与它通信呢这就需要用到QEMU的-serial参数和Linux的虚拟串口tty配对再通过一个叫做btproxy的工具桥接。更简单且官方推荐的方法是使用Zephyr的babblesim测试框架的一部分——btproxy。但首先我们需要确保QEMU以正确的方式暴露这个虚拟HCI接口。实际上当我们使用ninja run时它可能没有自动配置主机侧的蓝牙代理。我们需要手动操作。第一步以特定参数启动QEMU。退出当前运行的QEMU通常按CtrlA, X。我们使用一个更底层的命令来启动以便附加必要的设备参数cd ~/zephyrproject/zephyr ./scripts/support/run-qemu.sh -b qemu_cortex_a53 ~/zephyrproject/zephyr/samples/蓝牙/peripheral_hr/build/zephyr/zephyr.elf -serial mon:stdio -serial null -serial pipe:/tmp/bt-server这个命令做了几件事-serial mon:stdio: 将第一个串口控制台映射到标准输入输出。-serial null: 第二个串口不用。-serial pipe:/tmp/bt-server:关键将第三个串口映射到一个命名管道named pipe/tmp/bt-server。Zephyr的原始HCI驱动CONFIG_BT_HCI_RAW_RESERVE1正是使用这个UART设备来收发HCI数据包。现在QEMU在后台运行并创建了管道文件。第二步使用btproxy桥接管道到主机蓝牙栈。打开另一个终端窗口。我们需要先编译安装btproxy工具。它位于Zephyr项目的tools/btproxy目录下。cd ~/zephyrproject/zephyr/tools/btproxy pip3 install --user -r requirements.txt python3 setup.py build sudo python3 setup.py install或者更简单的方式是使用westcd ~/zephyrproject west build -p always -b native_posix tools/btproxy编译完成后运行btproxy连接刚才QEMU创建的管道sudo ./build/tools/btproxy/tools/btproxy/btproxy -t socket:/tmp/bt-serversudo是必需的因为btproxy需要创建系统级的HCI接口如hci0。-t socket:/tmp/bt-server指定传输层为Unix socket连接到我们的管道。如果成功你会在btproxy终端看到连接信息并且在主机上可以用hciconfig命令看到一个新的蓝牙设备hciconfig -a你应该能看到一个hciX例如hci1接口其地址可能与QEMU内Zephyr打印的地址一致通常是随机地址。4.3 验证蓝牙功能现在虚拟的蓝牙控制器已经“挂载”到你的Ubuntu系统上了。你可以像操作一个真实USB蓝牙适配器一样操作它。启动蓝牙服务确保宿主机的蓝牙服务正在运行。sudo systemctl start bluetooth开启并查询设备sudo hciconfig hci1 up # 将hci1替换为你实际看到的接口名 sudo hciconfig hci1应该显示UP RUNNING状态。使用蓝牙命令行工具扫描 在QEMU终端里Zephyr示例程序peripheral_hr应该正在广播设备名可能是Zephyr HR。在主机终端使用hcitool或bluetoothctl扫描sudo hcitool -i hci1 lescan你应该能看到一个名为Zephyr HR的设备在广播并显示其随机蓝牙地址。使用bluetoothctl连接可选bluetoothctl [bluetooth]# select hci1 [bluetooth]# scan on ... (找到Zephyr HR的设备地址例如 AA:BB:CC:DD:EE:FF) [bluetooth]# connect AA:BB:CC:DD:EE:FF如果连接成功你会在QEMU终端看到连接建立的日志并且在bluetoothctl里可以看到服务列表。至此你已经成功地在Ubuntu上的QEMU中运行了Zephyr并以polling模式驱动了蓝牙并且这个虚拟蓝牙设备能够被主机系统发现和交互。5. 调试技巧与常见问题排查5.1 典型问题与解决方案在实际操作中你几乎一定会遇到一些问题。下面是我总结的常见“坑”及其解决方法。问题1编译时出现找不到Python模块或west not found错误。原因Python环境或PATH未正确设置。解决确认pip3 install --user west已执行。确认~/.local/bin在PATH中echo $PATH。尝试使用python3 -m west代替west命令。对于其他缺失模块使用pip3 install --user 模块名安装。问题2ninja run启动QEMU后没有任何蓝牙相关日志或者很快退出。原因A目标板不支持蓝牙或配置未生效。qemu_cortex_m3板就是这种情况。解决A务必确认使用qemu_cortex_a53板并在cmake时显式传递了蓝牙相关配置如-DCONFIG_BTy。原因B镜像文件路径错误或构建失败。解决B在build目录下确认zephyr.zephyr文件已生成。使用west build -t run命令时它会在当前目录寻找build文件夹。问题3运行btproxy时提示权限不够或无法打开socket。原因/tmp/bt-server管道文件可能不存在或QEMU没有以正确参数启动。解决确认QEMU启动命令包含了-serial pipe:/tmp/bt-server。使用ls -l /tmp/bt-server.in /tmp/bt-server.out检查管道文件是否存在。它们应该是一对FIFO文件。确保btproxy命令使用了正确的路径socket:/tmp/bt-server。btproxy默认连接/tmp/bt-server-bridge所以我们的参数必须指定。尝试用sudo运行btproxy。问题4hciconfig看不到新的hci设备或者hcitool lescan扫描不到Zephyr设备。原因Abtproxy没有成功连接到管道或内部出错。解决A查看btproxy终端的输出信息。成功连接会显示Server connected等信息。如果有错误检查QEMU是否在运行管道路径是否正确。原因BZephyr应用程序没有成功启动广播。解决B查看QEMU终端输出。确认有Bluetooth initialized和Starting Advertising的日志。如果没有可能是应用程序本身构建或配置问题。尝试先编译一个最简单的蓝牙示例如samples/蓝牙/broadcaster进行测试。原因C主机蓝牙服务冲突。解决C有时主机自带的蓝牙适配器hci0会干扰。可以尝试暂时禁用它sudo hciconfig hci0 down测试完记得重新启用sudo hciconfig hci0 up。问题5如何调试Zephyr内部的蓝牙行为方法使用GDB连接QEMU进行源码级调试。在启动QEMU时添加-s -S参数例如在run-qemu.sh脚本的参数中添加。-s表示在1234端口开启GDB调试服务-S表示启动时暂停CPU。在另一个终端使用arm-none-eabi-gdb来自Zephyr SDK进行连接arm-none-eabi-gdb ~/zephyrproject/zephyr/samples/蓝牙/peripheral_hr/build/zephyr/zephyr.elf (gdb) target remote localhost:1234 (gdb) continue这样就可以设置断点、单步调试了对于分析复杂的polling驱动逻辑非常有用。5.2 性能优化与高级配置默认的polling模式会占用一个CPU核心进行忙等待这在模拟环境中可能不是问题但如果你想了解如何降低CPU占用或者更精细地控制polling行为可以调整以下配置调整轮询间隔纯粹的阻塞接收CONFIG_BT_RECV_BLOCKINGy没有间隔。但你可以结合使用CONFIG_BT_RECV_BLOCKINGn并实现一个自定义的、带有休眠的轮询线程。这需要修改应用代码在接收循环中加入k_sleep(K_MSEC(1))之类的调用来让出CPU。使用Tickless Kernel如果Qephyr配置了CONFIG_TICKLESS_KERNELy当系统空闲时CPU可以进入深度休眠即使驱动是polling模式在无事件时也能通过内核机制降低功耗在模拟环境中主要是降低CPU占用。选择不同的虚拟板除了qemu_cortex_a53也可以尝试qemu_nios2或qemu_riscv32等它们可能具有不同的虚拟外设和性能特性但蓝牙支持程度需要逐一测试。6. 项目扩展与进阶玩法成功运行基础示例后这个环境可以成为你强大的蓝牙协议开发和测试平台。1. 开发自定义蓝牙应用你不再局限于示例。可以在app目录下创建自己的Zephyr应用调用蓝牙APIGATT, GAP, L2CAP等在QEMU环境中快速迭代开发。编译时只需将路径指向你的应用目录即可。2. 测试不同蓝牙功能Zephyr示例目录下有很多蓝牙样例central_hr: 作为中心设备连接心率计。mesh: 蓝牙Mesh组网。direction: 蓝牙寻向。iso-broadcast: 蓝牙音频广播LE Audio。 你都可以用同样的方法在QEMU中运行和测试。对于需要多个设备交互的测试如Mesh你可以同时启动多个QEMU实例每个实例使用不同的管道文件如/tmp/bt-server1,/tmp/bt-server2然后运行多个btproxy实例桥接到不同的虚拟HCI接口hci1,hci2从而在宿主机上模拟多个蓝牙设备之间的通信。3. 集成自动化测试将上述流程脚本化。你可以编写一个Shell脚本或Python脚本自动完成编译、启动QEMU、启动btproxy、运行测试用例如使用bluetoothctl脚本或Python的pybluez库、收集日志、对比结果等一系列操作构建一个完整的CI/CD测试流水线。4. 深入研究驱动与协议栈由于所有代码都是开源的你可以随时修改Zephyr的蓝牙协议栈或hci_uart驱动。例如修改drivers/蓝牙/hci/hci_uart.c中的轮询逻辑添加更详细的调试日志或者尝试实现一种新的虚拟HCI传输方式。QEMU环境提供了完美的“修改-编译-测试”快速循环。搭建这个环境的过程本身就是一个深入理解嵌入式开发工具链、虚拟化技术、蓝牙协议栈和Zephyr RTOS构建系统的绝佳学习路径。它把复杂的硬件依赖抽象掉让你能聚焦于软件逻辑本身。当你需要将应用迁移到真实硬件时大部分代码和逻辑都可以复用只需要调整板级配置和驱动即可。这种“模拟先行”的开发模式在现代嵌入式开发中越来越成为提升效率的标准做法。

相关新闻