在 VxWorks 中,一个已经实例化(attach 完成)的串口驱动,应用层如何利用"中断方式"来读取串口发来的数据?
🔥 一、关键点:应用层不直接注册中断
很重要的一句话:
VxWorks 中,串口的中断处理由驱动(NS16550、Zynq UART、PPC DUART 等)内部完成,应用层无法直接中断绑定。
应用层只能看到一个设备文件:
/tyCo/0
/tyCo/1
/duart/0
/ttyS0
...
以及接口:
c
open()
read()
write()
ioctl()
🔥 二、驱动内部是如何处理接收中断的?
所有 VxWorks 的串口驱动都采用同样机制:
1. 中断到来时
UART → 触发 RX interrupt → CPU
2. 驱动 ISR(uartIsr()) 会做的事情:
- 读取 UART 数据寄存器
- 把收到的字节写入 环形缓冲区(ring buffer)
- 唤醒等待的任务(如果 read() 阻塞)
- 或者触发 TTY 驱动回调
⚠ 应用层无法直接执行 ISR
⚠ ISR 只存在于驱动内部
🔥 三、应用层如何"使用中断方式"接收数据?
中断方式不是由你注册。
中断方式 = 你调用 read() 时阻塞,驱动内部 ISR 填充数据并唤醒你。
VxWorks 提供三种读取方式:
✔ 方式 1:阻塞 read(最常见)
当串口收到数据,中断将数据放入驱动 ring buffer,并唤醒 read。
c
int fd = open("/tyCo/0", O_RDONLY, 0);
char buf[64];
int n;
while (1)
{
n = read(fd, buf, sizeof(buf)); // 中断到来后才返回
if (n > 0)
printf("recv: %.*s\n", n, buf);
}
特点:
- CPU 不需要轮询
- 中断到来 ISR → 数据入队 → read() 被唤醒
这是 纯中断驱动 的收数据方式。
✔ 方式 2:select() + read()(异步事件等待)
更专业的写法,用事件等待,不阻塞 CPU:
c
int fd = open("/tyCo/0", O_RDWR, 0);
fd_set readFds;
char buf[128];
while (1)
{
FD_ZERO(&readFds);
FD_SET(fd, &readFds);
select(fd+1, &readFds, NULL, NULL, NULL); // 中断填入数据后返回
if (FD_ISSET(fd, &readFds))
{
int n = read(fd, buf, sizeof(buf));
printf("recv: %.*s\n", n, buf);
}
}
这也是中断驱动,因为 select() 的唤醒来自中断处理。
✔ 方式 3:使用 TTY 模式(回调方式)
对于 /tyCo/x,串口被连接到 TTY 层
TTY 层本身支持:
- 行模式
- 信号控制
- 流量控制
实际上 TTY 也是以 中断接收 + 内部 FIFO 方式实现。
你可以使用:
c
ioctl(fd, FIOASYNC, TRUE);
ioctl(fd, FIOSETOWN, (int)taskIdSelf());
然后在 signal handler 处理数据(高级用法)。
🔥 四、真正注册中断的位置在哪?(不是应用层)
中断是驱动在 attach 阶段做的:
例如:
c
vxbIntConnect(pInst, 0, uartIsr, pDrvCtrl);
vxbIntEnable(pInst, 0);
或 BSP 老 API:
c
intConnect(INUM_TO_IVEC(intNum), uartIsr, pDrvCtrl);
intEnable(intNum);
ISR → TTY input routine → ring buffer → read()
应用程序 无需直接操作中断。
🔥 五、如果你自己写驱动,如何在驱动里处理中断?(附结构)
驱动内部典型流程:
c
LOCAL void uartIsr(void *pArg)
{
UART_DEV *pDrvCtrl = pArg;
while (UART_RX_READY())
{
char c = UART_READ_CHAR();
ringPut(&pDrvCtrl->rxRing, c);
}
/* 唤醒 read() 任务 */
selWakeupAll(&pDrvCtrl->selList);
}
应用层只需:
c
select()
read()