author: hjjdebugdate: 2026年 05月 17日 星期日 15:47:42 CSTdescrip: usb 串行口驱动庖丁解牛4: 数据收发的实现文章目录1. 数据收发的过程:2. 代码说明3.代码:4. 测试.1. 数据收发的过程:open/close 端口批量 OUT 端点 发送数据批量 IN 端点 接受数据内核串口层自动向上层 tty 提交收到的数据2. 代码说明.open 和 .close使用默认的 usb_serial_generic_open 和 usb_serial_generic_close。.read 和.write使用默认的 usb_serial_generic_read 和 usb_serial_generic_write。波特率设置, 也走默认函数.pl2303驱动就没干啥事!!为啥? 因为都是底层给干完了.它只是干了自己底层不能干,必需自己干的事.定义id匹配表,定义资源,返回接口个数.这就是pl2303 的三线驱动(Rxd,Snd,Gnd)的最简代码, 40行代码而完整版1千多行代码.理解这40行代码,才是精髓. 怪不得1000行代码不易看懂,因为不对应核心意义.差不多是废代码,冗余代码. 一大堆啰里扒嗦代码,把本质都掩盖了.其实,pl2303就是没干什么事, 核心工作都是usb_serial 子系统干的.如果你想在驱动中干点事情, 那就拦截usb_serial 子系统.3.代码:对于pl2303 驱动, 3线通讯模式(主机跟嵌入式通讯的方式), 带流控不太清楚(没有测试)这40行代码足以替代厂家的1079行代码$ cat pl2303.c#includelinux/module.h#includelinux/tty.h#includelinux/usb.h#includelinux/usb/serial.h#definePL2303_VID0x067B#definePL2303_PID0x2303staticconststructusb_device_idid_table[]{{USB_DEVICE(PL2303_VID,PL2303_PID)},{}};MODULE_DEVICE_TABLE(usb,id_table);staticintpl2303_calc_num_ports(structusb_serial*serial,structusb_serial_endpoints*ep){return1;//这里返回有一个接口, 系统会创建出/dev/ttyUSB0 一个节点}staticstructusb_serial_driverpl2303_minimal{.driver{.ownerTHIS_MODULE,.namepl2303_minimal,},.id_tableid_table,.num_bulk_in1,//设备可能有很多端点,但驱动可以不用,所以这里要配置.num_bulk_out1,//驱动向内核报资源,它可以在这里插一手..num_interrupt_in0,.calc_num_portspl2303_calc_num_ports,};staticstructusb_serial_driver*constserial_drivers[]{pl2303_minimal,NULL};module_usb_serial_driver(serial_drivers,id_table);MODULE_LICENSE(GPL);4. 测试.删除原有驱动 rmmod pl2303安装新编译驱动. insmod pl2303.ko测试串口收发完全正常.用自编的窗口应用程序精准控制测试.程序可参考:彻底搞懂搞定终端设置(用echo, cat , stty 也可以)$ ./serial_recv接收端已启动设备: /dev/ttyUSB0波特率: 115200按 CtrlC 退出第 1 次接收: 长度: 1 字节 | HEX: 68 | ASCII: h第 2 次接收: 长度: 1 字节 | HEX: 65 | ASCII: e第 3 次接收: 长度: 1 字节 | HEX: 6C | ASCII: l第 4 次接收: 长度: 1 字节 | HEX: 6C | ASCII: l第 5 次接收: 长度: 1 字节 | HEX: 6F | ASCII: o第 6 次接收: 长度: 1 字节 | HEX: 20 | ASCII:第 7 次接收: 长度: 1 字节 | HEX: 77 | ASCII: w第 8 次接收: 长度: 1 字节 | HEX: 6F | ASCII: o第 9 次接收: 长度: 1 字节 | HEX: 72 | ASCII: r第 10 次接收: 长度: 1 字节 | HEX: 6C | ASCII: l第 11 次接收: 长度: 1 字节 | HEX: 64 | ASCII: d第 12 次接收: 长度: 1 字节 | HEX: 0A | ASCII: \n$ ./serial_send准备发送字符串共 12 字节hello world发送次数1第 1 次发送成功写入 12 字节所有发送完成关闭串口那这里就要问几个问题,为什么我没有设置open, 就走了usb_serial_generic_open我没有在驱动中设置.write, 就调用了usb_serial_generic_write.usb_serial.c 与 pl2303.c 到底是什么关系?怎样实现的这种继承,补充,拦截关系?答:pl2303 注册驱动的时候, 里面有一个对应的驱动对象,是usb_serial 创建的,各函数指针已经有值了, 现在,pl2303如果有值,会覆盖原有值. 以后调用,就会走到pl2303函数如果pl2303 没有值,就不会覆盖旧值,还走以前的老路所以pl2303 只是一个锦上添花的事情, 大部分别人都准备好了.write 容易理解, 一路调用下去,最后通过URB 控制块,通过USB总线把数据发送给设备.我们read 的时候, 数据是怎么上来的? 会阻塞吗?答: read 的时候,我们读得是缓存中的数据, 如果缓存有数据,就直接给上层. 有多少返回多少.没有数据,会阻塞, 如果超过等待时间,就返回0字节. 需要等上层再次调用read.那缓存中的数据是怎么来的?缓存中的数据是usb控制器不断的发送bulk_in 指令获取到的. usb 控制器是个硬件,它不断的询问设备,有数据吗? 有数据就上. 控制器一但启动,通过usb总线上数的过程是不需要程序参与的.usb控制器为啥这样工作呢? 因为我们驱动配置了bulk_in 为1, 系统就会命令控制器, 给我时刻关注设备bulk_in 端点.控制器收到了数据,就会通知系统来取数, 系统在中断服务程序中,把数据存入缓冲区.在pl2303程序中,我们即没有看到对控制器的操控命令, 也没有看到中断服务程序都被隐藏到底层了. 所以感觉还是看得少了点. 但是,流程倒是想清楚了.自此,估计所谓的ch341 驱动, cp210x 驱动, 标准acm(抽象控制模型)等驱动,估计也都能用几行代码搞定.因为usb-serial核心框架已经有了,再加点小补充,适配自己的ID, 说不定读,写,控制都是标准协议.至于那些modem 信号控制, 本人这里没有环境, 主要还是根本就没有使用过, 可能各驱动需要单独写吧.不过我就不关心了.因为几乎用不到.以后几年前买的这个usb转串口线,就用我的40行代码小驱动了. 因为我了解了它的工作原理.我写了4篇博客想搞通usb转串口详细的工作原理,到最后收获了40行代码,整了个寂寞,深入浅出啊,这是1000行代码中过滤的.