保姆级教程:在NRF52840上实现USB虚拟串口,并每秒发送数据到PC和安卓手机

张开发
2026/4/19 8:21:29 15 分钟阅读

分享文章

保姆级教程:在NRF52840上实现USB虚拟串口,并每秒发送数据到PC和安卓手机
从零构建NRF52840 USB虚拟串口通信系统跨平台数据收发实战指南在嵌入式开发中稳定可靠的通信接口是连接物理设备与数字世界的桥梁。NRF52840作为Nordic Semiconductor旗舰级蓝牙SoC其内置的USB 2.0全速控制器为开发者提供了除蓝牙之外的另一条高效数据传输通道。本文将带您深入探索如何基于nRF5 SDK 17.0.2在NRF52840开发板上实现USB CDC ACM通信设备类抽象控制模型虚拟串口功能并构建完整的跨平台通信验证系统。1. 开发环境搭建与基础工程解析1.1 硬件准备与SDK配置NRF52840开发板选择上官方推出的PCA10056开发套件是最佳选择其板载USB接口和调试器可大幅简化开发流程。若使用第三方开发板需确保USB数据线D/D-已正确连接至NRF52840的P0.06(DP)和P0.08(DM)引脚开发板供电稳定USB供电或外部3.3V电源SWD调试接口可用开发环境建议采用Segger Embedded StudioNordic官方推荐或Keil MDKnRF5 SDK 17.0.2需从Nordic官网下载完整包J-Link驱动用于程序烧录与调试nRF Connect桌面工具用于协议栈烧录提示安装SDK时建议保持默认路径结构避免后续例程路径引用问题。SDK中的components和modules目录包含关键驱动和中间件代码。1.2 USB CDC ACM例程结构剖析SDK提供的usbd_ble_uart例程位于nRF5_SDK_17.0.2_d674dde\examples\peripheral\usbd_ble_uart \pca10056\s140\arm5_no_packs该工程已实现基础USB通信框架主要包含以下核心组件文件功能描述main.c应用主循环、外设初始化app_usbd_cdc_acm.cCDC ACM类实现sdk_config.h工程配置参数usbd_ble_uart_pca10056_s140.ld链接脚本关键配置参数集中在sdk_config.h#define APP_USBD_VID 0x1915 // Nordic Semiconductor的厂商ID #define APP_USBD_PID 0x521A // 产品ID #define APP_USBD_CDC_ACM_COMM_INTERFACE 0 #define APP_USBD_CDC_ACM_DATA_INTERFACE 12. 定时器系统与数据发送实现2.1 1Hz定时器模块集成在原有工程基础上添加精确的1Hz定时器需使用nRF5 SDK的app_timer模块。首先在工程中创建定时器相关变量#define TICK_1HZ_INTERVAL APP_TIMER_TICKS(1000) // 1秒间隔 APP_TIMER_DEF(m_1hz_timer_id); // 定时器实例 static nrf_atomic_u32_t m_1hz_event_flag; // 原子操作事件标志 static void timer_1hz_handler(void * p_context) { UNUSED_PARAMETER(p_context); nrf_atomic_u32_or(m_1hz_event_flag, 1); // 设置事件标志 }初始化函数应放在timers_init()中static void timers_init(void) { ret_code_t err_code; err_code app_timer_init(); APP_ERROR_CHECK(err_code); err_code app_timer_create(m_1hz_timer_id, APP_TIMER_MODE_REPEATED, timer_1hz_handler); APP_ERROR_CHECK(err_code); err_code app_timer_start(m_1hz_timer_id, TICK_1HZ_INTERVAL, NULL); APP_ERROR_CHECK(err_code); }2.2 主循环数据处理逻辑修改main()函数中的主循环添加事件检测与数据发送逻辑int main(void) { // ...原有初始化代码... static uint32_t tick_counter 0; char send_buffer[32]; for (;;) { // 处理USB事件队列 while (app_usbd_event_queue_process()) { /* Nothing to do */ } // 检查1Hz事件 uint32_t events nrf_atomic_u32_fetch_store(m_1hz_event_flag, 0); if (events) { tick_counter; // 格式化发送数据 int len snprintf(send_buffer, sizeof(send_buffer), Tick %lu\n, tick_counter); // 通过USB CDC ACM发送 if (m_cdc_acm_connected) { ret_code_t ret app_usbd_cdc_acm_write(m_app_cdc_acm, (const uint8_t *)send_buffer, len); if (ret ! NRF_SUCCESS) { NRF_LOG_ERROR(Send failed: 0x%X, ret); } } NRF_LOG_INFO(Sent tick: %lu, tick_counter); } idle_state_handle(); } }3. USB通信全流程配置与优化3.1 CDC ACM类深度配置在sdk_config.h中需确保以下关键配置已启用#define APP_USBD_CDC_ACM_ENABLED 1 #define APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE 0 #define APP_USBD_CDC_ACM_COMM_INTERFACE 0 #define APP_USBD_CDC_ACM_DATA_INTERFACE 1 #define NRF_SERIAL_ENABLED 1 #define NRF_LOG_BACKEND_UART_ENABLED 0 // 禁用UART日志后端USB描述符配置要点设备描述符包含厂商ID、产品ID和设备版本配置描述符定义接口和端点配置字符串描述符提供可读的设备信息3.2 数据传输稳定性保障措施实际项目中需考虑以下可靠性增强策略流量控制检查app_usbd_cdc_acm_write()返回值实现简单的重试机制使用NRF_ATOMIC_FLAG_SET/CLEAR管理发送状态错误恢复static void usb_error_handler(ret_code_t err_code) { if (err_code NRF_ERROR_INVALID_STATE) { NRF_LOG_WARNING(USB disconnected, reinitializing...); app_usbd_stop(); nrf_delay_ms(100); app_usbd_start(); } }缓冲区管理使用环形缓冲区存储待发送数据实现双缓冲机制避免数据覆盖4. 跨平台测试与验证4.1 Windows平台测试流程驱动安装现代Windows系统通常能自动识别CDC ACM设备如需手动安装使用SDK中的nRF5_SDK_17.0.2_d674dde\examples\usb_drivers串口工具配置推荐使用Tera Term或Putty关键参数波特率115200实际不影响USB CDC ACM通信数据位8停止位1流控制DTR/RTS使能数据验证应每秒收到Tick X格式的数据发送测试数据观察RTT Viewer输出4.2 Android平台集成方案Android端需要处理USB Host模式通信主要步骤修改安卓工程配置// 在CustomProber.java中匹配NRF52840的VID/PID private static final int NORDIC_VID 0x1915; private static final int CDC_ACM_PID 0x521A;权限声明uses-feature android:nameandroid.hardware.usb.host / uses-permission android:nameandroid.permission.USB_PERMISSION /通信核心逻辑private void setupUsbConnection(UsbDevice device) { UsbInterface intf device.getInterface(1); // 数据接口 UsbEndpoint endpointIn intf.getEndpoint(0); mConnection.claimInterface(intf, true); mWorkerThread new Thread(new Runnable() { public void run() { ByteBuffer buffer ByteBuffer.allocate(64); while (mRunning) { int len mConnection.bulkTransfer( endpointIn, buffer.array(), buffer.capacity(), 100); if (len 0) { String data new String(buffer.array(), 0, len); runOnUiThread(() - updateReceivedData(data)); } } } }); }5. 高级调试技巧与性能优化5.1 常见问题排查指南开发过程中可能遇到的典型问题及解决方案现象可能原因解决方法设备未被识别驱动未正确安装检查设备管理器手动指定驱动数据发送失败USB未连接或挂起检查连接状态重新枚举定时不准确低频时钟源未启用确认LFCLK源已配置Android无法连接权限未获取检查USB_HOST权限5.2 性能优化策略提高传输效率增大USB端点大小修改APP_USBD_CDC_ACM_DATA_EP_SIZE使用DMA传输减少CPU负载降低功耗// 在无通信时进入低功耗模式 if (!nrf_atomic_flag_get(m_data_pending)) { sd_app_evt_wait(); }内存优化使用nrf_memobj管理动态内存优化缓冲区大小平衡性能和内存占用实际项目中我曾遇到Android设备在某些厂商定制ROM上无法正常通信的情况。通过添加USB配置描述符的详细检查逻辑最终发现是某些设备对接口编号有特殊要求。这提醒我们跨平台兼容性测试的重要性——至少需要在3-5款不同品牌的Android设备上进行验证。

更多文章