4. 配置串口驱动

任务说明

在VxWorks BSP中配置串口驱动,核心目标是:编写串口底层驱动(字符输出与输入),并将其注册到I/O系统,使应用层可通过标准文件操作(open/read/write)访问串口。本案例基于VxWorks 7的BSP框架,以NS16550兼容UART为硬件模型,实现最小可用的驱动注册闭环。

环境与依赖

  • VxWorks 7 + Workbench,目标板使用NS16550兼容串口控制器(寄存器偏移:THR/RBR=0,IER=1,LCR=3,LSR=5)。
  • 依赖VxBus或传统BSP的寄存器映射宏(本案例直接使用物理地址映射,便于独立验证)。
  • 需要包含头文件:vxWorks.h、iosLib.h、tyLib.h、sysLib.h。

关键完整代码

/* uartDrv.c - 最小NS16550串口驱动,注册到I/O系统 */
#include <vxWorks.h>
#include <iosLib.h>
#include <tyLib.h>
#include <sysLib.h>

/* 寄存器偏移 */
#define UART_THR   0x00   /* 发送保持寄存器 */
#define UART_RBR   0x00   /* 接收缓冲寄存器 */
#define UART_IER   0x01   /* 中断使能 */
#define UART_LCR   0x03   /* 线路控制 */
#define UART_LSR   0x05   /* 线路状态 */
#define LSR_THRE   0x20   /* 发送保持寄存器空 */
#define LSR_DR     0x01   /* 数据就绪 */

/* 驱动设备结构 */
typedef struct {
    DEV_HDR  devHdr;      /* I/O系统设备头 */
    UINT32   baseAddr;    /* 寄存器基地址 */
} UART_DEV;

static UART_DEV uartDev;

/* 底层字符输出 */
static STATUS uartPutChar (UART_DEV *pDev, char ch)
{
    UINT32 lsr;
    /* 等待THR空 */
    do {
        lsr = sysInByte (pDev->baseAddr + UART_LSR);
    } while ((lsr & LSR_THRE) == 0);

    sysOutByte (pDev->baseAddr + UART_THR, (UINT8)ch);
    return OK;
}

/* 底层字符输入(非阻塞) */
static STATUS uartGetChar (UART_DEV *pDev, char *pCh)
{
    UINT32 lsr = sysInByte (pDev->baseAddr + UART_LSR);
    if ((lsr & LSR_DR) == 0)
        return ERROR;   /* 无数据 */
    *pCh = (char)sysInByte (pDev->baseAddr + UART_RBR);
    return OK;
}

/* 打开设备 */
static STATUS uartOpen (DEV_HDR *pDevHdr, FAST char *name, int mode)
{
    return OK;
}

/* 关闭设备 */
static STATUS uartClose (DEV_HDR *pDevHdr)
{
    return OK;
}

/* 读:轮询方式 */
static int uartRead (DEV_HDR *pDevHdr, char *buffer, int nBytes)
{
    UART_DEV *pDev = (UART_DEV *)pDevHdr;
    int count = 0;
    char ch;

    while (count < nBytes) {
        if (uartGetChar (pDev, &ch) == OK) {
            buffer[count++] = ch;
        } else {
            break;   /* 无更多数据则返回已读字节 */
        }
    }
    return count;
}

/* 写:轮询方式 */
static int uartWrite (DEV_HDR *pDevHdr, char *buffer, int nBytes)
{
    UART_DEV *pDev = (UART_DEV *)pDevHdr;
    int count;

    for (count = 0; count < nBytes; count++)
        uartPutChar (pDev, buffer[count]);

    return count;
}

/* 驱动函数表 */
static DEV_FUNCS uartFuncs = {
    uartOpen,
    uartClose,
    uartRead,
    uartWrite,
    NULL,   /* ioctl */
    NULL,   /* remove */
    NULL,   /* select */
    NULL,   /* unselect */
    NULL,   /* revoke */
    NULL,   /* truncate */
    NULL,   /* chmod */
    NULL,   /* chown */
    NULL,   /* rename */
    NULL    /* mkdir */
};

/* 初始化:映射寄存器并注册设备 */
STATUS uartDevInit (UINT32 physAddr, char *devName)
{
    /* 1. 映射物理地址到虚拟地址(假设BSP已建立映射,此处直接使用物理地址) */
    uartDev.baseAddr = physAddr;

    /* 2. 初始化UART:8N1,使能FIFO */
    sysOutByte (physAddr + UART_LCR, 0x03);   /* 8位数据,无校验,1停止位 */

    /* 3. 注册设备到I/O系统 */
    uartDev.devHdr.d_name = devName;
    if (iosDevAdd (&uartDev.devHdr, devName, &uartFuncs) != OK)
        return ERROR;

    return OK;
}

运行与验证

  1. 编译链接:将上述代码加入BSP工程编译,确保链接了iosLib和sysLib。
  2. 调用初始化:在BSP启动代码或shell中执行:
    uartDevInit (0xFE00000, "/tyCo/0");
    (地址按实际硬件替换)
  3. 验证输出:在shell中执行:
    fd = open("/tyCo/0", O_RDWR, 0);
    write(fd, "Hello UART\n", 11);
    close(fd);
    通过串口终端观察是否打印Hello UART。
  4. 验证输入:在shell中执行:
    fd = open("/tyCo/0", O_RDWR, 0);
    read(fd, buf, 10);
    在串口终端输入字符,确认read能返回输入的字节数。

通过条件:write返回写入字节数(11),且串口终端可见输出;read能正确返回从终端输入的字符数。若iosDevAdd返回OK且文件操作正常,则驱动注册成功。

常见问题

  • 寄存器地址错误:确认物理地址与数据手册一致,且BSP已建立MMU映射。
  • 字符丢失:轮询方式下若CPU速度远高于外设,需在uartPutChar中增加超时保护(本案例为最小实现,未加入超时)。
  • 设备名冲突:确保devName不与已有设备重复,否则iosDevAdd返回ERROR。