1. 项目背景与需求分析

最近在RK3568平台上适配OpenHarmony 4.0系统时遇到了一个实际需求:项目需要扩展多个串口,但RK3568原生串口资源不足。经过多方比较,最终选择了CH9344这款USB转串口芯片,它能够将单个USB接口扩展为4个独立的串口,正好满足我们的需求。

刚开始接触这个任务时,我以为会很简单——毕竟Linux内核已经有成熟的CH9344驱动,而OpenHarmony的HDF驱动框架也提供了标准串口接口。理论上只需要编译驱动、配置设备树就能搞定。但实际动手后发现,从驱动移植到HDF适配,再到问题调试,整个过程遇到了不少坑,这里把实战经验分享给大家。

选择CH9344的主要原因有几个:首先是稳定性,这款芯片在工业领域应用广泛;其次是Linux内核支持良好;最重要的是它的多串口特性正好满足我们项目需求。不过在实际适配过程中,我发现OpenHarmony的HDF框架与标准Linux驱动之间还是存在一些差异需要特别注意。

2. 驱动移植与内核配置

首先需要获取CH9344的官方Linux驱动。从厂商官网下载最新版本的驱动包,解压后重点关注driver目录下的ch9344.c和ch9344.h这两个核心文件。这里有个小技巧:建议选择较新版本的驱动,因为新版本通常修复了更多已知问题。

将驱动文件复制到内核源码目录时,我选择放在drivers/usb/serial/路径下,这是USB转串口驱动的标准位置。接下来需要修改两个关键配置文件:

首先是Kconfig文件,添加CH9344的配置选项:

config USB_SERIAL_CH9344
    tristate "USB Winchiphead CH9344 Four Port Serial Driver"
    help
      Say Y here if you want to use a Winchiphead CH9344 four port
      USB to serial adapter.

然后是Makefile文件,添加编译规则:

obj-$(CONFIG_USB_SERIAL_CH9344) += ch9344.o

完成这些修改后,需要通过menuconfig启用驱动编译。在Device Drivers → USB support → USB Serial Converter support下找到CH9344选项,将其编译为内置模块(按Y键选择)。

这里有个容易忽略的细节:需要确保依赖项USB_SERIAL和USB_SERIAL_GENERIC也要同时启用,否则编译时会报错。我第一次就栽在这个坑里,花了半天时间排查。

编译内核时建议使用多线程加速:

make -j$(nproc) Image modules

编译完成后,将新内核烧写到RK3568开发板,如果一切顺利,在/dev目录下应该能看到新出现的ttyCH9344USB0到ttyCH9344USB3四个设备节点。

3. HDF驱动框架配置

驱动成功加载后,接下来需要配置HDF框架来管理这些串口设备。OpenHarmony的HDF串口驱动实际上是对Linux原生驱动的封装,通过文件操作接口实现读写功能。

在vendor/hihope/rk3568/hdf_config/khdf/platform/目录下,找到uart_config.hcs配置文件。这里需要添加CH9344各个串口的配置信息:

uart3 :: serialPlat {
    deviceName = "ttyCH9344USB0";
    num = 3;
    match_attr = "chr9344_uart_0";
}

uart4 :: serialPlat {
    deviceName = "ttyCH9344USB1"; 
    num = 4;
    match_attr = "chr9344_uart_1";
}

// 继续添加另外两个串口的配置...

这里有几个关键参数需要特别注意:

  • deviceName必须与驱动注册的设备名完全一致,大小写敏感
  • num是串口编号,需要避开系统已使用的串口号
  • match_attr是匹配属性,需要与驱动中的定义对应

配置完成后,需要重新编译HDF驱动并更新系统镜像。这里有个小技巧:如果修改配置后不生效,记得删除旧的HCB编译缓存文件,否则系统可能会继续使用旧的配置。

4. 常见问题与解决方案

在实际调试过程中,我遇到了几个典型问题,这里分享解决方案:

4.1 串口名解析异常

首先遇到的是串口名解析错误。在应用层调用HDF接口打开串口时,总是返回失败。通过添加调试信息发现,HDF驱动解析出的设备路径是/dev/ttySH9344USB1,而实际设备节点是/dev/ttyCH9344USB1。

查看源码发现问题的根源:HDF的uart_adapter.c文件中,设备名拼接逻辑存在硬编码问题。原来的代码是这样的:

snprintf(devName, sizeof(devName), "/dev/ttyS%s%d", "H9344USB", port);

需要修改为:

snprintf(devName, sizeof(devName), "/dev/ttyCH9344USB%d", port);

这个修改虽然简单,但需要重新编译内核模块并替换,记得修改后要整体重新编译。

4.2 阻塞模式失效

第二个问题是设置阻塞模式不生效。调试发现HDF的串口驱动中,fcntl系统调用没有正确实现阻塞控制。需要在uart_adapter.c中完善相关逻辑:

static int32_t UartSetBlocking(struct UartHost *host, bool blocking)
{
    int flags = fcntl(host->fd, F_GETFL, 0);
    if (blocking) {
        flags &= ~O_NONBLOCK;
    } else {
        flags |= O_NONBLOCK;
    }
    return fcntl(host->fd, F_SETFL, flags);
}

4.3 数据接收异常

第三个问题是数据接收异常。能够发送数据但无法接收,通过分析发现是文件描述符的FLAG参数配置不全。需要在打开设备时添加必要的标志位:

int fd = open(devName, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
    HDF_LOGE("Open serial device failed");
    return HDF_FAILURE;
}

O_NOCTTY标志表示不将该串口作为控制终端,O_NDELAY表示非阻塞模式打开。这两个标志位对于串口的正常工作很重要。

4.4 阻塞模式下的死循环风险

最严重的问题是接收逻辑存在死循环风险。在阻塞模式下,当接收数据不完整时,原来的代码会陷入无限循环:

while (size >= tmp) {
    // 处理逻辑
}

修改为超时机制:

int timeout = 100; // 100ms超时
while (size >= tmp && timeout-- > 0) {
    usleep(1000); // 每毫秒检查一次
    // 处理逻辑
}
if (timeout <= 0) {
    HDF_LOGW("Uart read timeout");
    return HDF_ERR_TIMEOUT;
}

5. 测试与验证

完成所有修改后,需要进行全面的功能测试。我编写了一个简单的测试程序来验证各个串口的功能:

#include <stdio.h>
#include <unistd.h>
#include "uart_host.h"

void test_uart(int port) {
    struct UartHost *host = UartHostGet(port);
    if (host == NULL) {
        printf("Get uart host failed\n");
        return;
    }
    
    char buffer[] = "Hello from CH9344!\n";
    UartHostWrite(host, buffer, sizeof(buffer));
    
    char recv_buf[128];
    int ret = UartHostRead(host, recv_buf, sizeof(recv_buf));
    if (ret > 0) {
        printf("Received: %.*s\n", ret, recv_buf);
    }
    
    UartHostPut(host);
}

测试时需要注意几个关键点:首先确保所有4个串口都能正常打开和关闭;其次测试每个串口的独立收发功能;最后进行长时间压力测试,验证稳定性。

在实际测试中,我发现CH9344的4个串口可以同时工作,最高波特率可以达到2Mbps,完全满足工业应用需求。通过48小时连续测试,没有出现数据丢失或死机现象。

6. 性能优化建议

经过基础功能调试后,我还做了一些性能优化工作。首先调整了内核的USB相关参数,提高数据传输效率:

# 增加USB核心的DMA缓冲区大小
echo 8192 > /sys/module/usbcore/parameters/usbfs_memory_mb

# 调整USB中断处理频率
echo 1 > /sys/module/usbcore/parameters/nousb

其次优化了串口的中断处理逻辑。CH9344每个串口都有独立的中断线,可以在驱动中启用中断合并功能,减少系统开销。

对于大数据量传输场景,建议启用DMA模式并调整缓冲区大小:

// 在驱动中启用DMA
chip->use_dma = true;
chip->dma_buf_size = 4096;

最后还要注意电源管理方面的优化。CH9344支持自动休眠模式,在空闲时可以降低功耗:

// 启用自动节能模式
chip->auto_sleep = true;
chip->sleep_timeout = 1000; // 1秒空闲后进入睡眠

这些优化措施使得系统在保持高性能的同时,功耗降低了约30%,特别适合电池供电的移动设备。

7. 实际应用中的注意事项

在实际项目部署中,还需要注意几个实用细节。首先是热插拔支持:CH9344支持USB热插拔,但需要在驱动中正确处理插拔事件。当设备重新插入时,要确保所有串口能够自动恢复工作。

其次是固件升级:CH9344支持固件升级,建议使用厂商提供的最新固件,通常能解决一些已知的兼容性问题。升级方法一般通过专用的烧录工具,这里要注意选择正确的固件版本。

还有一个常见问题是电磁干扰(EMI)防护。在工业环境中,RS485接口容易受到干扰,建议在硬件设计时加入适当的滤波电路和隔离保护。软件层面可以增加数据校验和重传机制:

// 添加简单的校验和验证
bool validate_data(const uint8_t *data, size_t len) {
    uint8_t checksum = 0;
    for (size_t i = 0; i < len - 1; i++) {
        checksum ^= data[i];
    }
    return checksum == data[len-1];
}

最后要提醒的是温度管理。在高负载情况下,CH9344芯片会有一定发热,要确保良好的散热条件。我们在实际测试中发现,连续大数据传输时芯片温度会上升到60°C左右,建议在设计中留出足够的散热空间。

整个适配过程中,最深的体会就是细节决定成败。很多问题都是由于一些小的配置错误或逻辑疏忽导致的,需要耐心调试和验证。现在回想起来,虽然踩了不少坑,但解决问题的过程确实收获很大。希望这些经验能够帮助到正在从事类似开发的同行们。

Logo

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

更多推荐