简介:VxWorks下串口通信示例程序是一份面向嵌入式开发者的工程源码,完整演示了实时系统中串口设备的数据收发方法。压缩包共25个文件,核心为C语言源代码,另含编译生成的中间文件、工程配置文件、调试符号表与列表文件,整体仅26KB,结构清晰,便于快速定位所需内容。工程围绕TestUart任务展开,演示了标准串口驱动的应用方法:从打开端口、配置波特率与数据格式,到读写数据、检查返回值、释放资源,还涉及信号控制与中断处理、多任务同步等要点,并通过日志函数输出状态,涵盖错误处理与调试环节。已有765人学习下载,适合希望掌握VxWorks串口驱动模型或从事设备通信开发的工程师参考,通过研读代码可快速迁移至实际板卡的串口调试,也可作为嵌入式课程设计的示例参照。
1. VxWorks串口通信示例程序:不是“填满代码就能跑”的模块
接手某个基于VxWorks的嵌入式项目时,串口通信这块栽了跟头:示例程序看着完整,编译也通过,一跑起来却收不到数据,偶尔收到一帧还乱码。最后发现根本不是波特率或校验位的问题,而是驱动层、缓冲区和任务上下文三件事没对齐。这篇笔记基于一套可直接编译的 VxWorks 串口收发示例程序,覆盖从 BSP 驱动初始化到应用层 open/read/write 的完整链路,重点拆解 ioctl 参数、阻塞模式、缓冲区边界和自测验证方法。适合刚上手 VxWorks、需要把串口调通的嵌入式开发者;如果你已经在项目里跑过串口但间歇性丢数据,也能在里面找到对应的排查方向。
2. 理解VxWorks串口驱动结构:为什么应用层代码不能绕过BSP
2.1 两段式驱动:tyCo底层驱动与tty层转发
VxWorks 的串口驱动不像 Linux 那样把 tty 驱动放到统一框架里,而是分成了紧贴硬件(SIO)的底层驱动和提供缓冲、行规则处理的 tty 驱动。底层部分通常在 BSP 里,文件一般叫sysSerial.c、tyCoDrv.c这类,负责寄存器读写、中断使能、收发 FIFO 操作。上层 tty 驱动则往上提供tyCoDevCreate、tyLibInit这类接口,让应用层通过标准的open、read、write访问串口设备。
| 分层 | 职责 | 典型接口/文件 |
|---|---|---|
| 硬件层 | 寄存器读写、FIFO、中断处理 | BSP 中sysSerial.c,xxxSioInit |
| 驱动层 | 收发缓冲、中断到信号量转发 | tyCoDrv.c、tyLib.c |
| 设备层 | 创建设备节点,绑定中断 | tyCoDevCreate("/tyCo/0", ...) |
| 应用层 | 打开、读写、参数配置 | open("/tyCo/0", O_RDWR, 0) |
这个分层的意义在于:如果应用层出了问题,先判断是设备节点没创建成功,还是驱动缓冲没工作,还是 BSP 中断向量配置不对。实际项目中常见的一个做法是:先用devs命令确认/tyCo/0、/tyCo/1这类设备节点已经存在,再用i命令查看中断是否注册成功,而不是一上来就检查应用代码。很多“串口不工作”的问题,其实卡在设备节点根本没有被tyCoDevCreate创建出来。
2.2 console与业务串口的区别
VxWorks 里默认的 console 串口由 BSP 参数(比如CONSOLE_DEV)指定,通常是/tyCo/0,用于输出printf和 Shell 调试信息。业务串口一般不用 console 那个通道,另开/tyCo/1或/tyCo/2。如果业务串口和 console 用的是同一个物理串口,会出现调试输出和业务数据混在一起的情况,接收端根本无法解析。我一般会建议:调试走 console(/tyCo/0),业务数据走/tyCo/1,两边物理上分开,哪怕板子上只有一个串口,也优先保证业务完整性。
区分之后,示例程序里打开的就是/tyCo/1,波特率 115200、8 数据位、无校验、1 停止位。这个组合在大多数场景下够用,也是很多工控设备默认的通信参数。注意一点:VxWorks 里对 9 位数据位、多停止位这类特殊参数的支持取决于驱动层实现,手册上写支持不代表当前 BSP 一定支持,最稳的方式还是先跑一个回环,确认参数确实生效。
2.3 中断到信号量:读数据的底层路径
底层串口驱动收到一个字节后,会进入中断处理函数,把数据放进 tty 层的接收缓冲区;应用层read时实际上是从这个缓冲区取数据。这套路径里最容易忽略的是缓冲区大小:默认 tty 接收缓冲区如果只有几百字节,而业务方一次性发来几十 KB,那么丢数据几乎是必然的。示例程序里建议把TYCO_BUFSIZE这类宏调大到 4096 或更大,但不建议无限大,因为每个缓冲都会占内存,而 VxWorks 的内存通常比较紧张。具体在哪个文件里改,不同 BSP 有差异,常见位置是板级头文件里对tyCo驱动的配置段。
3. 串口收发示例代码:逐行拆开open、read、write的参数与边界
3.1 示例代码主体
下面这段代码是完整可编译的串口收发例子,跑在 VxWorks 的 Shell 环境下即可。它做了三件事:打开/tyCo/1、设置串口参数、循环回显接收到的数据。
#include <vxWorks.h> #include <stdio.h> #include <fcntl.h> #include <ioLib.h> #include <tyLib.h> #define SERIAL_DEV "/tyCo/1" #define BAUD_RATE 115200 #define MAX_BUF_LEN 1024 STATUS serialTest(void) { int fd; char buf[MAX_BUF_LEN]; int nRead; int total = 0; /* 打开串口设备,O_RDWR表示读写,O_NOCTTY防止被当作控制终端 */ fd = open(SERIAL_DEV, O_RDWR, 0); if (fd == ERROR) { printf("open %s failed, errno = 0x%x\n", SERIAL_DEV, errnoGet()); return ERROR; } /* 设置波特率,部分BSP需要先调用FIOFLUSH清空收发缓冲 */ if (ioctl(fd, FIOBAUDRATE, BAUD_RATE) == ERROR) { printf("set baudrate failed\n"); close(fd); return ERROR; } printf("serial test start, fd = %d\n", fd); while (1) { nRead = read(fd, buf, MAX_BUF_LEN); if (nRead == ERROR) { printf("read error, errno = 0x%x\n", errnoGet()); break; } if (nRead > 0) { write(fd, buf, nRead); total += nRead; printf("recv %d bytes, total %d\n", nRead, total); } else { /* 没有数据时让出CPU,避免任务空转 */ taskDelay(1); } } close(fd); return OK; }这段代码的逻辑很直白:open之后如果成功,ioctl设置波特率,然后进入while(1)循环读数据。read在 VxWorks 默认是阻塞模式,如果没有数据,任务会挂在读操作上。但这里read返回 0 时用taskDelay(1)让出 CPU,是为了防止极端情况下 read 立即返回但没有数据导致的空转。errnoGet()是 VxWorks 获取错误码的接口,检查 errno 而不是直接凭返回值判断,能少走很多弯路。
3.2 参数调整:找到适合你硬件的那组配置
上面的代码里,波特率是写死的,实际项目中应该由配置文件或者入参传入。除了波特率,示例里没有设置数据位、校验位和停止位,因为 VxWorks 的ioctl接口里这些通常通过FIOSETOPTIONS或底层驱动私有命令配置。一个常见做法是先查询驱动是否支持SIO_RAW_MODE这类模式,再决定要不要关掉行规则处理:默认 tty 层可能会有回车换行转换,这会让二进制数据无法原样收发。
int opt = 0; ioctl(fd, FIOSETOPTIONS, O_RAW); ioctl(fd, FIOFLUSH, 0);如果你在项目里遇到“收到的字节总是少了 0x0D”或者“0x0A 变成 0x0D 0x0A”,就是行规则没关。O_RAW选项告诉驱动层不要做任何转换,原样搬运。配置顺序我一般固定为:open → FIOBAUDRATE → FIOSETOPTIONS(O_RAW) → FIOFLUSH,这样能避免上一次会话残留的缓冲数据干扰新配置。
3.3 编译链接与冒烟测试
# 在VxWorks开发环境下,把serialTest.c编译为.o,然后链接进VxWorks镜像 ccpentium -c -I$(WIND_BASE)/target/h serialTest.c -o serialTest.o # 或者单独编译成可下载模块,用ld命令加载到目标机 ld < serialTest.o编译完成后,在目标机 Shell 执行serialTest,把串口 TX 和 RX 用一根杜邦线短接,也就是回环自测:程序发什么数据出去,立刻就能在本地读到。如果回环能收到自己发的数据,说明驱动链路通;如果收不到,问题大概率在底层驱动或 BSP 配置,而不是应用代码。这一步能快速划分问题边界。
4. 避坑:串口调试中四个高频事故现场
4.1 波特率设置不生效,回环收到乱码
现象:ioctl(fd, FIOBAUDRATE, 115200)返回 OK,回环测试时接收方看到乱码。
原因:部分 BSP 的串口驱动在ioctl设置波特率时,只改驱动参数,没有真正重写 UART 寄存器的分频值。另外如果open之后没有调用FIOFLUSH,接收缓冲区里的旧数据会和新的波特率数据混在一起。
解决:先调用ioctl(fd, FIOFLUSH, 0)清空缓冲,再设置波特率;如果依然乱码,去 BSP 的串口底层驱动里找波特率配置函数,检查分频寄存器是否被正确赋值。一个更快的验证方法是直接用示波器看 TX 引脚的波形,计算实际波特率。
4.2 read永久阻塞,任务卡死在PEND状态
现象:任务调用read后一直不返回,i命令查看任务状态显示PEND。
原因:VxWorks 默认串口 read 是阻塞模式,没有数据时任务会一直等待。如果外部设备不发数据,任务永远卡住,连带整个业务逻辑停摆。
解决:改成非阻塞模式,或者用超时机制。示例代码里如果不想改驱动,可以在业务侧加一个taskDelay轮询,但更好的做法是设置O_NONBLOCK:
int flags = 0; ioctl(fd, FIOSETOPTIONS, O_NONBLOCK);设置非阻塞之后,read没有数据会立即返回 0 或 -1,业务侧要结合taskDelay控制轮询频率,避免死循环吃满 CPU。我在实际项目中更推荐后面第 5 章里写的select方案。
4.3 高频收发丢字节,缓冲区只有几百字节
现象:外部设备以 100Hz 的频率每帧发送 200 字节,程序处理后发现丢帧。
原因:默认 tty 接收缓冲区太小,中断往缓冲区写数据的速度快于应用层read取走数据的速度,越积越多,最终溢出丢弃。另一个隐患是任务优先级过低,read还没来得及执行,下一个中断又来了。
解决:第一步,调大驱动接收缓冲区。示例项目里TYCO_BUFSIZE或同类宏从默认值调到 4096;第二步,把串口读任务优先级提到中断任务之下、普通任务之上,比如taskSpawn时把优先级设为 120 左右。调大缓冲是缓解措施,真正根治还是要保证应用层及时消费数据。
4.4 多个任务同时read同一串口,数据被随机抢走
现象:两个任务都执行了open("/tyCo/1"),一个收命令,一个收数据,结果两边收到的东西交叉混乱。
原因:VxWorks 的串口设备允许多次 open,read时从同一个缓冲区取数据,各任务拿到的数据没有归属边界,谁先调度谁就取走一段。
解决:串口在多任务环境里只允许一个任务作为唯一读者。常见做法是:第一个任务open之后,后续任务用ioctl检查或直接约定业务上只有一个任务操作串口;如果必须多个任务协作,就在应用层加一个分发信号量,所有read得到的原始数据先放到一条队列里,再由分发任务按帧头解析路由。示例程序里的串口只服务一个业务模块,不搞多读者。
5. 进阶改造:把轮询式收发改成select事件驱动,并验证时序
如果示例程序里的轮询方式已经满足需求,这个进阶改造可以跳过。但如果你面对的是“既要接传感器,又要回命令,还得处理告警”的多路串口场景,轮询会让代码乱成一团。更稳的模式是用select统一等待多个 fd,有数据才处理,没有数据就阻塞让出 CPU。
#include <selectLib.h> void serialSelectTask(void) { fd_set rdSet; struct timeval timeout; int fd; fd = open("/tyCo/1", O_RDWR, 0); if (fd == ERROR) return; while (1) { FD_ZERO(&rdSet); FD_SET(fd, &rdSet); timeout.tv_sec = 1; timeout.tv_usec = 0; /* VxWorks下用select_s避免与C库select命名冲突 */ if (select_s(fd + 1, &rdSet, NULL, NULL, &timeout) == ERROR) { printf("select error\n"); break; } if (FD_ISSET(fd, &rdSet)) { /* 有数据可读,执行定制化收发处理 */ handleSerialEvent(fd); } } }改造后的逻辑:select_s在 1 秒超时内等待 fd 可读;如果可读,进入handleSerialEvent做收发处理;如果超时,循环继续。相比轮询,select 版本不会阻塞任务,也不会空转 CPU。超时时间可以根据业务实时性要求调整,一般 500ms 到 1s 比较合适。
验证这套改造是否可靠,最简单的办法还是回环:把 TX/RX 短接,让程序每 100ms 发出一个带序号的测试帧,同时统计接收到的序号是否连续。如果序号连续,说明时序正确;如果出现跳号,说明 select 触发后 read 和 write 之间仍有丢数据窗口。从那以后我每次接手 VxWorks 串口项目,都强制走一遍“回环 + 序号连续性”验证,不再凭感觉判断驱动好坏。这套示例程序覆盖的正是这条完整的验证链路,下载下来照着跑一遍,比翻手册快得多,希望帮到你。
本文还有配套的精品资源,点击获取