3304| 2
|
[项目] 双USB串口数据交换器 |
串口是最常见的接口,因为它足够简单,几乎在所有的调试场合都能看到它的身影。从编程的角度来说,这种接口的代码已经非常成熟,从 C 到 Python 都能够支持这种接口。实际工作生产中最常见的就是USB转串口。美中不足的是,这USB转串口在编程的时候存在着如下缺陷: 1. 某些USB转串口设备需要额外安装驱动才能使用; 2. 当同一台电脑存在多个COMPort时,查找特定的设备会比较麻烦; 3. 打开串口后很难得知串口另外一端的设备是否已经准备好; 4. 传输速度有限制,最常见的波特率只有115200,意味着一秒只能传输11KB左右的数据。 针对上述问题,这次使用南京沁恒微电子公司出品的 CH32V208 制作一个双USB串口数据交换器(DualUSB SERIAL DATA EXCHANGER, 简称DUSDE)。CH32V208是一款基于32位RISC-V设计的无线型微控制器,配备了硬件堆栈区、快速中断入口,在标准RISC-V基础上大大提高了中断响应速度。搭载V4C内核,加入内存保护单元,同时降低硬件除法周期。除了片上集成2Mbps低功耗蓝牙BLE通讯模块、10M以太网MAC+PHY模块、CAN控制器等接口之外还带有2个USB2.0全速设备+主机/设备接口。这次的双USB串口数据交换器就是将自身模拟为2个 USB 串口设备分别连接到两台电脑上,从而实现数据传输的功能。 从上面的系统结构图可以看到,CH32V208WBU6支持两个USB2.0Full Speed设备,其中一个可以作为 HOST或者Device,另外一个只能作为 Device 使用。我们通过编程的方式,让他们都工作在Device模式下,下图就是我们设备的框图。 针对前面的需要额外驱动的问题,通过编程在DUSDC上实现USBCDC协议,这样在Windows8 及其以上的系统无需额外安装驱动。Windows识别所属的 Class 之后,会自动加载内置驱动。 针对多个串口和需要检测对面设备是否准备好的问题,我们预定义了一些命令,当用户使用2022波特率打开串口后,能够响应如下命令。
例如,系统中存在 COM1 到 COM4 多个串口,程序枚举每一个串口,打开串口后设定波特率为2022,之后从该串口发送“?ACV”命令,如果能够得到“USB1”或者“USB2”这样的回复,会表明当前为1号或者2号USB端口; 再比如,我们用设备连接两台电脑,USBPort1有程序打开过端口,并且用 “?ACV”查询过当前设备,那么USBPort 2这端程序以2022波特率打开端口后,再发送“?CFG”就能得到“REDY”的回复,表示对面的端口(USB1)已经准备好,反之如果收到“NRDY”则表示另外端口没有收到过“?ACV”命令; 最后,在程序看来数据使用串口传输,但是因为所有的传输都是在USB中进行,USB端口之间的数据也是在内存中进行的交换,相比传统串口速度会有很大的提升。实际测试表明在在使用超级终端程序的zmodem协议传输文件时,速度可达300KB/S以上。同时因为没有串口的发送和采样过程,传输过程发生错误的概率极低。 上面就是为什么要设计DUSDC,以及它的优点。下面介绍DUSDC的具体实现。 首先是硬件部分。整体设计非常简单,并没有太多的元件: 核心部分就是基于CH32V208的最小系统,其中的S4是Download按钮,需要下载Firmware时,先按住按钮然后插入USB接口,之后再抬起按钮,使用 WCHISPTool即可下载。 USB 使用的是Type-B母头,选择原因是这种结构非常稳固,最大限度保证连接的可靠。需要注意的是J1是预留的取电接口,在连接两台PC时,为了避免5V电压不同可以将J1断开,这样设备就只能通过USB2进行取电。 通过一颗TLV1117来实现5V到3.3V的转换,5V来自USB 端口。预留了UART1作为调试接口,基本上所有的问题都能通过输出 Log 来解决。 此外,板子上预留了一个SPI1出来的SPINOR 接口,如果有记录数据的需求,可以考虑在该位置焊接SPINOR芯片或者PSRAM。 因为板子上没有多余的元件,所以布线也比较简单: 3D渲染结果: 成品PCB: 硬件确定之后就可以着手设计软件了。CH32V208的示例程序有两个USB转UART的例子,一个是USBDB(全速设备控制器)的例子,另外一个是USBFS(全速主机/设备控制器,这里用作全速设备控制器)。因为功能上有差别,这两个控制器名称也有差别,编程比较麻烦。类似CH567的话,一个是USB1另外一个是USB2感觉就会好很多。第一个工作是将两个代码融合在一起通过编译。 例如,当前是USBDB,工作基本流程是: 1. 报告HOST当前是 USB CDC 设备; 2. 在 USBDB OUT的Endpoint收取来自HOST的数据,收下之后该OUTEndpoint设置为 NAK 回复状态,这样HOST不会继续对该OUTEndpoint 发送数据; 3. 在主循环中轮询,如果有收到数据,那么查询USBFSIN Endpoint 是否 Busy,如果Busy继续等待,否则通过USBFS的INEndpoint 将数据发送出去。 注意:USB 中描述的 OUT 和IN 是以HOST的CPU 为准的,对于运行着 Windows的x86来说,OUT 是指从CPU到单片机方向(Write),反之数据是从单片机到CPU(Read)。 上述过程中, 代码位置简述: 1. 在 usb_desc.c 有添加iSerialNumber定义,这样当使用同样的设备时,每次插入会维持相同的串口号。例如,第一次使用Windows分配为为 COM10,如果iSerialNumber为空,那么再次插入可能会被分配为COM11,但是iSerialNumber不为空,再次插入仍然会是 COM10; 2. usb_endp.c 处理USBDB 收到的主机OUT的数据,接收到的数据会放在 USB1_Tx_Buf[] 中。这部分代码可以看作是两部分:一部分是处理特别 COMMAND(以2022波特率打开串口之后进入特别 COMMAND 模式);另外一部分是处理转发数据的代码,就是在USB1_Tx_Counter=USB1BufLen记录收到的数据长度
1. 主机的轮询发送,在main.c中,主要动作是将数据通过 USBFS_Endp_DataUp() 函数转发给USBFS 的 Port 上。
此外,特别命令模式处理代码如下,通过USBD_ENDPx_DataUp() 将数据返回到USBDB对的USB Port上。 2
上面就是USBDB的USBPort 处理流程,USBFS的USB Port处理逻辑相同,但是代码表达上差异很大。下面是实物: |
© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed