CH9344在OpenHarmony HDF框架下的硬件适配与内核协同实战

在工业控制和物联网设备开发中,多串口扩展是一个常见且关键的需求。RK3568这类高性能处理器虽然功能强大,但原生串口资源往往难以满足复杂应用场景的需求。CH9344作为一款成熟的USB转四串口芯片,为系统扩展提供了可靠的硬件基础。本文将深入探讨如何在OpenHarmony的HDF驱动框架下,实现CH9344芯片的完整驱动适配,并解决实际开发中遇到的内核协同问题。

1. 深入理解HDF驱动框架与CH9344芯片特性

OpenHarmony的HDF驱动框架采用分层解耦的设计理念,旨在为不同硬件设备提供统一的驱动接口。对于串口设备,HDF框架在用户态提供标准的UART API,而在内核态则通过适配层与Linux内核的TTY子系统进行交互。

CH9344是一款高度集成的USB转串口芯片,能够将单个USB接口扩展为四个独立的串口。该芯片在Linux内核中已有成熟的驱动支持,通过注册为USB串口设备,会在/dev目录下生成相应的tty设备节点,如ttyCH9344USB0到ttyCH9344USB3。

关键特性对比:

特性CH9344芯片HDF UART框架
接口类型USB 2.0标准串口API
支持串口数4个独立串口无限制(依赖硬件)
数据位宽5、6、7、8位5、6、7、8位
流控支持RTS/CTSRTS/CTS
中断处理内置中断机制依赖内核中断子系统

在实际适配过程中,开发者需要理解HDF框架的UART驱动实际上是通过文件操作接口(open、read、write、ioctl)与内核态的tty设备进行交互。这种设计意味着只要CH9344驱动正确注册生成了tty设备,HDF框架就能无缝地进行操作。

2. CH9344驱动移植与内核配置

将CH9344驱动移植到OpenHarmony内核是整个过程的基础步骤。首先需要获取官方的Linux驱动源码,通常包含ch9344.c和ch9344.h两个主要文件。

驱动移植步骤:

  1. 源码放置:将驱动文件复制到内核源码的适当目录,通常是drivers/usb/serial/目录下
  2. 修改Kconfig:在drivers/usb/serial/Kconfig中添加配置选项:
    config USB_SERIAL_CH9344
        tristate "USB Winchiphead CH9344 Four Port Serial Driver"
        depends on USB
        help
          Say Y here if you want to use a Winchiphead CH9344 four port
          USB to serial adapter.
    
  3. 修改Makefile:在drivers/usb/serial/Makefile中添加编译规则:
    obj-$(CONFIG_USB_SERIAL_CH9344) += ch9344.o
    
  4. 内核配置:通过make menuconfig或直接修改defconfig文件,确保启用CONFIG_USB_SERIAL_CH9344选项

注意:在编译内核前,务必确认USB串口支持(CONFIG_USB_SERIAL)和相应的USB控制器驱动已经启用,否则CH9344驱动将无法正常工作。

完成驱动移植后,编译并烧写新内核。系统启动后,可以通过dmesg命令查看驱动加载状态,正常情况下应该能看到CH9344驱动初始化的相关信息,并在/dev目录下看到生成的tty设备节点。

3. HCS配置与设备节点管理

HDF使用HCS配置文件来描述硬件设备信息,这是驱动匹配和设备初始化的关键。对于CH9344这样的多串口设备,需要为每个生成的串口单独配置。

完整的HCS配置示例:

device_uart_ch9344_0 :: uart_device {
    driver_name = "ttyCH9344USB";  // 设备名称前缀
    num = 0;                      // 设备编号
    match_attr = "rockchip_rk3568_uart_ch9344_0"; // 匹配属性
}

device_uart_ch9344_1 :: uart_device {
    driver_name = "ttyCH9344USB";
    num = 1;
    match_attr = "rockchip_rk3568_uart_ch9344_1";
}

device_uart_ch9344_2 :: uart_device {
    driver_name = "ttyCH9344USB";
    num = 2;
    match_attr = "rockchip_rk3568_uart_ch9344_2";
}

device_uart_ch9344_3 :: uart_device {
    driver_name = "ttyCH9344USB";
    num = 3;
    match_attr = "rockchip_rk3568_uart_ch9344_3";
}

配置参数详解:

  • driver_name:必须与内核中注册的设备名前缀完全一致,否则HDF无法正确打开设备
  • num:设备编号,与driver_name组合形成完整的设备名(如ttyCH9344USB0)
  • match_attr:用于设备匹配的唯一标识符,必须与device_info.hcs中的配置相对应

在device_info.hcs中,需要为每个CH9344串口添加设备信息:

device_uart_ch9344_0 :: device {
    deviceMatchAttr = "rockchip_rk3568_uart_ch9344_0";
    serviceName = "ch9344_uart_service_0";
}

// 同样为其他三个串口添加配置

重要提示:修改HCS配置后,需要删除旧的HCB编译产物并重新编译,否则更改可能不会生效。HCB是HCS的二进制编译结果,系统优先使用已存在的HCB文件。

4. 实战问题排查与性能优化

在实际开发过程中,我们遇到了几个典型问题,这些问题的解决方案对于类似项目具有参考价值。

4.1 设备打开失败问题

最初发现应用层无法正确打开CH9344串口,通过在内核驱动中添加调试信息,发现设备名解析异常。HDF框架中的uart_adapter.c存在一个逻辑缺陷:它使用静态数组存储设备名,但在多设备情况下会发生覆盖。

修复方案:

// 修改前的代码(有bug)
static int32_t GetUartDeviceName(struct UartHost *host, uint32_t port, char *deviceName)
{
    if (snprintf_s(deviceName, NAME_LEN, NAME_LEN - 1, "/dev/%s%u", 
                  host->driverName, port) < 0) {
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

// 修改后的代码
static int32_t GetUartDeviceName(struct UartHost *host, uint32_t port, char *deviceName, size_t nameLen)
{
    if (snprintf_s(deviceName, nameLen, nameLen - 1, "/dev/%s%u", 
                  host->driverName, port) < 0) {
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

4.2 阻塞模式失效问题

HDF UART驱动中的阻塞设置最初是伪实现,没有实际效果。这会导致在读取数据时无法正确阻塞等待。

修复方案:

// 在uart_adapter.c中完善阻塞设置逻辑
static int32_t UartSetBlocking(struct UartHost *host, bool blocking)
{
    int flags = fcntl(host->fd, F_GETFL, 0);
    if (flags < 0) {
        return HDF_FAILURE;
    }
    
    if (blocking) {
        flags &= ~O_NONBLOCK;
    } else {
        flags |= O_NONBLOCK;
    }
    
    if (fcntl(host->fd, F_SETFL, flags) < 0) {
        return HDF_FAILURE;
    }
    
    return HDF_SUCCESS;
}

4.3 数据接收异常问题

在测试中发现CH9344串口可以发送数据但无法接收数据。通过分析源码,发现是文件描述符的FLAG参数配置不正确,缺少必要的读写权限设置。

修复方案:

// 修改设备打开逻辑,确保设置正确的标志位
host->fd = open(deviceName, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (host->fd < 0) {
    HDF_LOGE("Open UART device %s failed", deviceName);
    return HDF_FAILURE;
}

// 立即设置正确的阻塞模式
UartSetBlocking(host, true);

4.4 性能优化建议

对于高速数据传输场景,建议采用以下优化措施:

  1. 调整内核缓冲区大小:通过ioctl设置合适的发送和接收缓冲区大小
  2. 使用DMA传输:启用CH9344的DMA功能,降低CPU占用率
  3. 中断合并:调整中断触发阈值,减少中断次数
  4. 电源管理:合理配置USB自动挂起和唤醒策略

5. 系统集成与测试验证

完成驱动开发和问题修复后,需要进行全面的系统集成测试。测试内容包括功能测试、性能测试、稳定性测试和兼容性测试。

测试用例示例:

  1. 基本功能测试:验证每个串口都能正常打开、关闭、读取和写入数据
  2. 参数配置测试:测试不同波特率(9600、115200、921600等)、数据位、停止位和校验位的配置
  3. 流控测试:验证RTS/CTS流控功能的正确性
  4. 并发测试:同时操作多个CH9344串口,测试系统资源竞争情况
  5. 长时间稳定性测试:连续运行24小时以上,检查是否有内存泄漏或资源耗尽问题

测试工具推荐:

  • minicom:用于基本的串口通信测试
  • pyserial:编写自动化测试脚本
  • 自定义测试程序:针对特定应用场景的专项测试

在实际项目中,我们开发了一个综合测试工具,能够自动执行上述测试用例并生成详细的测试报告。这个工具不仅提高了测试效率,还能快速定位和复现问题。

通过完整的测试验证,CH9344在OpenHarmony HDF框架下的驱动表现稳定,四个串口都能达到标称的通信性能,满足工业控制场景的严格要求。

6. 总结与最佳实践

CH9344在OpenHarmony上的成功适配,为多串口扩展需求提供了一个可靠的解决方案。在整个开发过程中,我们积累了一些宝贵经验:

首先,深入理解内核机制是基础。只有清楚掌握Linux TTY子系统的工作原理和USB设备枚举过程,才能快速定位和解决驱动问题。

其次,重视配置细节。HDF框架对设备名称、匹配属性等配置非常敏感,稍有偏差就可能导致驱动加载失败。

第三,充分利用调试工具。dmesg、lsusb、strace等工具是驱动开发的利器,能够提供宝贵的问题线索。

最后,测试要全面系统。不仅要做功能测试,还要进行性能、稳定性和异常情况测试,确保驱动在各种场景下都能可靠工作。

对于正在从事类似项目的开发者,建议从简单的单设备驱动开始,逐步扩展到复杂场景。同时,积极参与开源社区讨论,往往能获得意想不到的帮助和启发。OpenHarmony的驱动生态还在不断完善中,每个开发者的贡献都在推动这个生态变得更好。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐