在T3开发板上实现SylixOS最小系统(五)——实现T3串口驱动开发


在SylixOS中,不管是调试串口还是通信串口都是以TTY设备的形式注册到内核中使用,每一个串口设备都在/dev/ 目录下有一个对应的ttySx设备名的设备,所以在进行串口驱动开发的时候先介绍一下SylixOS的TTY子系统。我们串口驱动开发最终一定得创建TTY设备。

1 SylixOS的TTY子系统

在 SylixOS 系统中,终端是一种字符型设备,它有多种类型,通常使用 tty 来简称各种类型的终端设备。tty 是 Teletype 的缩写,Teletype 是最早出现的一种终端设备,很像电传打字机,是由 Teletype 公司生产的。
串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。在 SylixOS 中终端设备是以文件的方式被管理,在系统完成串口设备的创建之后,可以在系统的/dev/目录下看到相应的 tty 设备,一般命名为ttySx(x 表示序号)。对 tty 设备的操作流程与普通文件操作流程相似。
如下图所示:
在这里插入图片描述

  • BSP在启动的过程中也会打开调试串口的tty设备,并且将其文件描述符设置为内核标准输入、标准输出和标准错误这三个文件描述符。

————————————————————————分割线——————————————————————————————————
文件描述符
文件描述符的本质:内核中,进程打开文件IO信息指针数组的一个下标。 当我们通过描述符操作文件的时候,在pcb中找到files_ struct结构体指针,进而找到结构,找到结构体中的数组,以描述符作为下标获取到文件描述信息的地址,通过描述信息操作文件。

  • SylixOS下文件描述符的定义以及标准输出、标准输入、标准错误:
    在这里插入图片描述

也就是说一个进程运行起来默认会打开三个文件:标准输入,标准输出,标准错误,这三个文件占据的描述符就是: 0-标准输入, 默认从键盘读取信息;1-标准输出,默认将输出结果输出至终端;,2-标准错误,默认将输出结果输出至终端。

  • SylixOS中的标准文件系统描述(BASE_T3/libsylixos/SylixOS/mktemp/mkdemo/bsp/SylixOS/bsp/bsplnit.c):
/*********************************************************************************************************
** 函数名称: halStdFileInit
** 功能描述: 初始化目标系统标准文件系统
** 输 入  : NONE
** 输 出  : NONE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
#if LW_CFG_DEVICE_EN > 0

static VOID  halStdFileInit (VOID)
{
    int     iFd = open("/dev/ttyS0", O_RDWR, 0);

    if (iFd >= 0) {
        ioctl(iFd, FIOBAUDRATE,   SIO_BAUD_115200);
        ioctl(iFd, FIOSETOPTIONS, (OPT_TERMINAL & (~OPT_7_BIT)));       /*  system terminal 8 bit mode  */

        ioGlobalStdSet(STD_IN,  iFd);
        ioGlobalStdSet(STD_OUT, iFd);
        ioGlobalStdSet(STD_ERR, iFd);
    }
}

#endif                                                                  /*  LW_CFG_DEVICE_EN > 0        */

————————————————————————分割线——————————————————————————————————
回到正题。

2. 串口硬件原理

2.1 串口通信原理

串行接口简称串口,也称串行通信接口,是采用串行通信方式的扩展接口。串口是计算机领域最简单的通信接口,也是使用最广泛的通信接口。
串行接口(Serial Interface)是指数据按位顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信,但传送速度较慢,串行通讯的距离可以从几米到几千米。根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。单工是指在任何时刻都只能进行单向通信,如进程通信中的管道。半双工是指同一时刻只能进行单方向数据传输,如 I2C 总线,在同一时刻只能进行读或写操作。而全双工是指能够在任何时刻都可以进行双向通信,如串口。

2.2 串口通信参数

  • 串口通信的参数有波特率、数据位、停止位和奇偶校验位等。对于两个进行通信的端口,这些参数必须匹配。

1)波特率:用于衡量通信速度的参数,具体表示每秒钟传送的 bit 的个数。当提到时钟周期时,即指波特率。在通信过程中传输距离和波特率成反比。
2)数据位:用于衡量通信中实际数据位的参数,数据位的标准值有 5/6/7/8 位。数据位的具体设置取决于传送的信息类型。例如,对于标准的 ASCII 码是 0~127,因此数据位可设为 7 位。扩展的 ASCII 码是 0~255,则数据位设置为 8 位。
3)停止位:用于表示单个包的最后一位,典型的值有 1,1.5 和 2 位。停止位不仅表示传输的结束,而且提供计算机校正时钟机会。因此停止位的位数越多,时钟同步的容忍程度越大,同时数据传输率也越慢。
4)校验位:在串口通信中一种简单的检错方式。有四种检错方式:奇/偶校验以及高/低校验。对于奇和偶校验的情况,串口会对校验位进行设置,用该值确保传输的数据有奇数个或者偶数个逻辑高位。对于高/低校验位来说,高位和低位不是用来进行数据的检查,而是简单置位逻辑高或者逻辑低进行校验。

  • 串行通信又可分为异步通信与同步通信两类。

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是设备间进行异步通信的关键模块。其作用如下:
1) 处理数据总路线和串行口之间的串/并、并/串转换;
2) 通信双方只要采用相同的帧格式和波特率,就能在未共享时钟信号的情况下,仅用两根信号线(Rx 和 Tx)就可以完成通信过程;
3)采用异步方式,数据收发完毕后,可通过中断或置位标志位的方式通知微控制器进行处理,大大提高微控制器的工作效率。
相比于异步通信,同步通信需要额外提供时钟线,以保证数据传输时,发送方和接收方能够保持完全的同步,因此,要求接收和发送设备必须使用同一时钟信号。

2.3 串口通信工作方式

由于 CPU 与串口缓存区之间数据是按并行方式传输,而串口 FIFO 与外设之间按串行方式传输,因此在串行接口模块中,必须要有“Receive Shifter”(串→并)和“Transmit Shifter” (并→串)。
在数据输入过程中,数据按位从外设进入接口的“Receive Shifter”,当“Receive Shifter” 中已接收完 1 个字符的全部位后,数据就从“Receive Shifter”进入“数据输入寄存器”。CPU从“数据输入寄存器”中读取接收到的字符(并行读取,即 D7~D0 同时被读至累加器中)。 “Receive Shifter”的移位速度由“接收时钟”确定。
在数据输出过程中,CPU 把要输出的字符(并行地)送入“数据输出寄存器”,“数据输出寄存器”的内容传输到“Transmit Shifter”,然后由“Transmit Shifter”移位,把数据按位地送到外设。“Transmit Shifter”的移位速度由“发送时钟”确定,因此,在两个设备之间进行串口通信时,必须将两个设备的波特率设置一致才能正常通信。
接口中的“控制寄存器”用来容纳 CPU 送给此接口的各种控制信息,这些控制信息决定接口的工作方式。
“状态寄存器”的各位称为“状态位”,每一个状态位都可以用来指示数据传输过程中的状态或某种错误。例如,状态寄存器的 D5 位为“1”表示“数据输出寄存器”空,D0 位表示“数据输入寄存器满”,D2 位表示“奇偶检验错”等。
能够完成上述“串/并”转换功能的电路,通常称为“通用异步收发器”( UART:Universal Asynchronous Receiver and Transmitter ),典型的芯片有:Intel 8250/8251,16c550。

3 串口驱动

3.1 串口驱动实现方式

在 SylixOS 中,串口驱动的实现有三种方式:轮询、中断、DMA(直接存储器访问)。下面简单介绍这三种方式的特点:

  • 轮询方式
    轮询方式主要是每隔一段时间对各种设备进行轮询,查询设备有无处理要求,若有处理要求则进行相应处理。由此可见,若设备无处理要求,则 CPU 仍然会查询设备状态。而轮询的过程将会占据 CPU 的一部分处理时间,因此,程序轮询是一种效率较低的处理方式。
    串口的轮询实现就是通过不停的轮询状态寄存器的状态判断是否有数据需要接收或发送。
  • 中断方式
    中断方式是采用当数据到来或要发送数据时,通过中断的方式通知 CPU 去处理接收或发送的数据。这种方式相较于轮询方式,节省了无数据时 CPU 仍去查询的设备状态的时间花费,提高了 CPU 的使用效率。
    串口的中断实现是通过使能串口中断并在中断服务函数中判断中断状态,然后完成数据的收发操作。
  • DMA 方式
    DMA 方式是指数据在内存和设备之间能够直接进行数据的传输,而无需 CPU 的干预。
    在 SylixOS 中,首先需要实现 DMA 驱动,才能使用 DMA 进行内存和设备之间数据的搬运。
    DMA在将数据从设备搬运到内存后会产生DMA 中断,并通知 CPU 数据已经接收完成, CPU 会在中断处理函数中对数据进行处理。发送过程与接收过程类似。
    串口 DMA 实现方式需要 DMA 驱动的支持,相关实现可以参考 DMA 子系统章节。
    在 SylixOS 中,通常情况下,使用中断方式实现串口数据的收发。

3.2 串口驱动实现

在 SylixOS 中,串口驱动编写需要用户自行管理大部分数据。一般编写顺序是首先初始化串口通道的私有数据,然后进行硬件寄存器的初始化,最后向内核注册串口相关资源,包括串口驱动的实现方法和中断等。

3.2.1 创建串口通道 sioChanCreate

在系统启动过程中,驱动首先会调用 sioChanCreate 创建一个串口通道,该函数是在串口驱动文件中定义的,函数原型如下:

#include <SylixOS.h> 
SIO_CHAN  *sioChanCreate (INT  iChannelNum) 
  • 此函数成功返回串口通道结构体指针;
  • 参数 iChannelNum 是串口通道号。

串口通道创建函数主要完成串口的初始化,以及串口中断的安装,并返回串口通道结构体指针。在SylixOS中,使用SIO_CHAN 数据结构来表示一个SIO通道。串口通道结构体定义的详细描述如下:

#include <SylixOS.h> 

typedef struct sio_chan { 

 SIO_DRV_FUNCS   *pDrvFuncs;
 
 }  SIO_CHAN; 
  • 参数 pDrvFuncs:串口驱动方法集。
    这个结构体比较简单,其中只有一个成员,表示SIO驱动操作集。SIO_DRV_FUNCS 结构体就表示了SIO驱动操作集,这个驱动方法集提供了对串口进行操作的方法。主要工作即是实现该结构体中定义的函数方法,然后将实现的函数赋于该结构体中的成员指针。并最终通过串口通道创建函数返回。

3.2.2 串口驱动方法集 pDrvFuncs

结构体定义的详细描述如下:

#include <SylixOS.h> 

struct sio_drv_funcs {

  INT    (*ioctl)
         (SIO_CHAN   *pSioChan,  
         INT   cmd, 
          PVOID   arg)
          
  INT    (*txStartup)
         (SIO_CHAN   *pSioChan);
         
  INT    (*callbackInstall)
         (SIO_CHAN   *pSioChan, 
           INT   callbackType,  
            VX_SIO_CALLBACK callback, 
              PVOID callbackArg);
              
  INT    (*pollInput)
         (SIO_CHAN   *pSioChan,  
          PCHAR   inChar);
          
  INT    (*pollOutput)
         (SIO_CHAN   *pSioChan,  
          CHAR   outChar);
  };
  typedef struct sio_drv_funcs   SIO_DRV_FUNCS;

  • ioctl:串口通道控制函数,实现对SIO通道的一些控制,比如打开、关闭、设置硬件参数等;
  • txStartup:串口通道发送函数,实现发送数据。
  • callbackInstall:串口通道安装回调函数,SIO驱动中需要通过此回调将系统缓冲区发送和接收数据操作接口记录在驱动中,SylixOS并没有为BSP提供相应的API来直接操作系统数据缓冲区,所以只能通过这种间接的方式来记录内核中操作系统缓冲区的函数指针,然后在驱动中使用。
  • pollInput:串口通道轮询接收函数
  • pollOutput:串口通道轮询发送函数

对应结构体 sio_drv_funcs,在BASE_T3/libsylixos/SylixOS/system/util/sioLib.h 中定义了对应的宏。

1. sioIoctl

宏 sioIoctl 定义如下:

#define sioIoctl(pSioChan, cmd, arg) \ 
 ((pSioChan)->pDrvFuncs->ioctl(pSioChan, cmd, arg)) 

该宏原型分析:

  • 此宏成功返回 ERROR_NONE,失败返回 PX_ERROR;
  • 参数 pSioChan 是串口通道指针;
  • 参数 cmd 是控制命令,其取值下表所示;
控制命令含义
SIO_BAUD_SET设置波特率参数
SIO_BAUD_GET获取串口波特率参数
SIO_MODE_SET设置通道模式
SIO_MODE_GET获取通道模式
SIO_HW_OPTS_SET设置线控参数
SIO_HW_OPTS_GET获取线控参数
SIO_OPEN打开串口命令
SIO_HUP关闭串口命令
SIO_SWITCH_PIN_EN_SET设置额外模式
SIO_SWITCH_PIN_EN_GET获取额外模式
  • 参数 arg 是上层传递给底层的参数。

通过调用 sioIoctl 并设置相应串口控制命令,可以对串口进行设置。在使用过程中,对串口波特率,线控参数进行设置的情况比较多,关于串口波特率和线控参数设置的简单介绍如下:
1)SIO_BAUD_SET ,波特率设置:驱动通过调用 sioIoctl 并设置 cmd 参数为 SIO_BAUD_SET,然后设置参数 arg 的数值即可修改串口的波特率。常用的波特率的值有 9600,115200 等。
2)SIO_HW_OPTS_SET ,线控参数设置:串口的线控参数较多,常见的线控参数有数据位、停止位、奇偶校验位等。在 /libsylixos/SylixOS/system/util/sioLib.h 文件中定义了下表所示的宏。

标志说明
CLOCAL忽略调制调解器状态行
CREAD启动接收
CS55 位数据位
CS66 位数据位
CS77 位数据位
CS88 位数据位
HUPCL最后关闭时断开
STOPB2 位停止位,否则为 1 位
PARODD奇校验,否则为偶校验

驱动可以通过赋于参数 arg 为 CS8 | STOPB 设置串口为 8 位数据位 2 个停止位。

2. sioTxStartup

宏 sioTxStartup 定义如下:

#define sioTxStartup(pSioChan) \ 
 ((pSioChan)->pDrvFuncs->txStartup(pSioChan)) 

该宏原型分析:

  • 此宏成功返回 ERROR_NONE,失败返回 PX_ERROR;
  • 参数 pSioChan 是串口通道指针。

因为串口发送数据时需要启动串口,而 sioTxStartup 就是用来启动串口数据发送,当发送成功后会触发串口中断,中断根据缓存中是否还有数据来决定是否继续发送,当缓存清空后串口将恢复初始状态,并等待下一次传送的启动。

3. sioCallbackInstall

宏 sioCallbackInstall 定义如下:

#define sioCallbackInstall(pSioChan, callbackType, callback, callbackArg) \ 
     ((pSioChan)->pDrvFuncs->callbackInstall(pSioChan, callbackType, \  
                                  callback, callbackArg)) 

  • 此宏成功返回 ERROR_NONE,失败返回 PX_ERROR;
  • 参数 pSioChan 是串口通道指针;
  • 参数 callbackType 是回调类型,其取值如下表所示;
回调类型说明
SIO_CALLBACK_GET_TX_CHAR获取传输字符
SIO_CALLBACK_PUT_RCV_CHAR将接收数据放入终端缓冲区
SIO_CALLBACK_ERROR回调出错
  • 参数 callback 是回调函数指针;
  • 参数 callbackArg 是传给回调函数的参数。

4. sioPollInput

宏 sioPollInput 定义如下:

#define sioPollInput(pSioChan, inChar) \ 
 ((pSioChan)->pDrvFuncs->pollInput(pSioChan, inChar)) 

该宏原型分析:

  • 此宏成功返回 ERROR_NONE,失败返回 PX_ERROR;
  • 参数 pSioChan 是串口通道指针;
  • 参数 inChar 是轮询模式接收的数据。

5.sioPollOutput

宏 sioPollInput 定义如下:

#define sioPollOutput(pSioChan, thisChar) \ 
 ((pSioChan)->pDrvFuncs->pollOutput(pSioChan, thisChar)) 

该宏原型分析:

  • 此宏成功返回 ERROR_NONE,失败返回 PX_ERROR;
  • 参数 pSioChan 是串口通道指针;
  • 参数 thisChar 是轮询模式发送的数据。

在 tty 设备驱动创建过程中将会调用上面提供的宏,完成串口的设置以及启动串口数据发送。

3.2 我们T3开发板的串口驱动具体实现

在我们之前drivers/uart目录下新建sio.c和sio.h如下图所示:

在这里插入图片描述
开始编写sio.c文件内容


/* 首先先定义一个SIO通道全局变量 */

static SIO_CHAN  uartSioChan;

/* 再定义一个SIO通道全局变量 */

static SIO_DRV_FUNCS  uartSioDrvFunc;

/* 用SIO驱动操作集来初始化SIO通道 */

SIO_CHAN  *uartSioChanCreate (VOID)
{
    uartSioChan.pDrvFuncs = &uartSioDrvFunc;

    return  &uartSioChan;
}

/* 实现SIO驱动中的三个操作集接口  */
static SIO_DRV_FUNCS  uartSioDrvFunc = {
    .ioctl = uartSioIoctl,
    .txStartup = uartStartup,
    .callbackInstall = uartSioCbInstall,
};

接下来就实现这三个函数。

3.2.1 sioIoctl函数的具体实现

uartSioIoctl 这个函数需要实现SIO通道的打开、关闭、硬件参数设置、波特率设置等等。此函数成功返回 ERROR_NONE,失败返回 PX_ERROR。

/* 1.uartSioIoctl  这个函数需要实现SIO通道的打开、关闭、硬件参数设置、波特率设置等等*/

static INT  uartSioIoctl (SIO_CHAN  *pSioChan, INT  iCmd, PVOID  pArg)
{
    switch (iCmd) {
    case SIO_BAUD_SET:     /* 设置波特率参数  */
        *((LONG *)pArg) = 115200;
        break;

    case SIO_BAUD_GET:     /* 获取串口波特率参数 */
        *((LONG *)pArg) = 115200;
        break;

    case SIO_HW_OPTS_SET:    /* 设置线控参数 */
        *((LONG *)pArg) = CS8;  /* 设置8位数据位 */
        break;

    case SIO_HW_OPTS_GET:   /* 获取线控参数 */
        *((LONG *)pArg) = CS8;
        break;

    case SIO_OPEN:  /*打开串口命令*/
        break;

    case SIO_HUP:    /* 关闭串口命令 */
        break;

    default:
        _ErrorHandle(ENOSYS);
        return  (ENOSYS);
    }
    return  (ERROR_NONE);
}

我们用到的控制命令以及其含义

控制命令含义
SIO_BAUD_SET设置波特率参数
SIO_BAUD_GET获取串口波特率参数,我们的波特率是115200
SIO_HW_OPTS_SET设置线控参数,设置硬件参数,比如数据位,奇偶校验位,停止位等
SIO_HW_OPTS_GET获取线控参数 ,获取当前硬件设置的参数
SIO_OPEN打开SIO通道,一般这里会调用硬件的 初始化代码
SIO_HUP关闭串口命令

3.2.2 uartStartup函数的实现

uartStartup是SIO驱动中的发送函数, 这个接口的逻辑:就是从系统发送缓冲区中不停的取出数据, 然后调用串口发送接口发送,直到系统发送缓冲区中没有要发送的数据了就返回。此函数成功返回 ERROR_NONE,失败返回 PX_ERROR。

/* 2. uartStartup:SIO驱动中的发送函数了。
 * 这个接口的逻辑:就是从系统发送缓冲区中不停的取出数据,
 * 然后调用串口发送接口发送,直到系统发送缓冲区中没有要发送的数据了就返回
 */
static INT  uartStartup (SIO_CHAN  *pSioChan)
{
    CHAR  cChar;

    while (!uartGetTxChar(pTxArg, &cChar)) {
        uartPutChar(cChar);
    }

    return  (ERROR_NONE);
}

3.2.3 uartSioCbInstall函数实现

SylixOS没有为BSP提供相应的API来直接操作系统缓冲区的接口,而是在tty设备创建时将内核中的读写系统缓冲区的函数指针传入SIO驱动中的callbackInstall 接口, 驱动需要自己保存这两个函数指针和其对应的参数,我们可以在驱动中定义全局变量用来保存。

/*  3. uartSioCbInstall SylixOS没有为BSP直接提供操作系统缓冲区的接口,
 * 而是在tty设备创建时将内核中的读写系统缓冲区的函数指针传入SIO驱动中的callbackInstall 接口,
 * 驱动需要自己保存这两个函数指针和其对应的参数,我们可以在驱动中定义全局变量用来保存   */

static INT (*uartGetTxChar)(PVOID  pArg, PCHAR  pcChar);
static INT (*uartPutRcvChar)(PVOID  pArg, CHAR  cChar);
static PVOID  pTxArg;
static PVOID  pRxArg;
/*
 * 在callback回调函数被调用的时候,通过以下方法保存内核中的读写函数指针:
 */

static INT  uartSioCbInstall (SIO_CHAN  *pSioChan,
                              INT  iCallbackType,
                              VX_SIO_CALLBACK  callbackRoute,
                              PVOID  pvCallbackArg)
{
    switch (iCallbackType) {
    case SIO_CALLBACK_GET_TX_CHAR:    /* 获取传输字符 */
        uartGetTxChar = (INT (*)(PVOID, PCHAR))callbackRoute;
        pTxArg = pvCallbackArg;

        break;

    case SIO_CALLBACK_PUT_RCV_CHAR:  /*  将接收数据放入终端缓冲区  */
        uartPutRcvChar = (INT (*)(PVOID, CHAR))callbackRoute;
        pRxArg = pvCallbackArg;

        break;

    default:   /* 回调出错 */
        _ErrorHandle(ENOSYS);
        return  (PX_ERROR);
    }

    return  (ERROR_NONE);
}

3.2.4 sio.c源码

#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <linux/compat.h>
#include "uart.h"

static SIO_CHAN  uartSioChan;
static INT (*uartGetTxChar)(PVOID  pArg, PCHAR  pcChar);
static INT (*uartPutRcvChar)(PVOID  pArg, CHAR  cChar);
static PVOID  pTxArg;
static PVOID  pRxArg;

/* 1.uartSioIoctl  这个函数需要实现SIO通道的打开、关闭、硬件参数设置、波特率设置等等*/

static INT  uartSioIoctl (SIO_CHAN  *pSioChan, INT  iCmd, PVOID  pArg)
{
    switch (iCmd) {
    case SIO_BAUD_SET:     /* 设置波特率参数  */
        *((LONG *)pArg) = 115200;
        break;

    case SIO_BAUD_GET:     /* 获取串口波特率参数 */
        *((LONG *)pArg) = 115200;
        break;

    case SIO_HW_OPTS_SET:    /* 设置线控参数 */
        *((LONG *)pArg) = CS8;  /* 设置8位数据位 */
        break;

    case SIO_HW_OPTS_GET:   /* 获取线控参数 */
        *((LONG *)pArg) = CS8;
        break;

    case SIO_OPEN:  /*打开串口命令*/
        break;

    case SIO_HUP:    /* 关闭串口命令 */
        break;

    default:
        _ErrorHandle(ENOSYS);
        return  (ENOSYS);
    }
    
    return  (ERROR_NONE);
}

/* 2. uartStartup:SIO驱动中的发送函数了。
 * 这个接口的逻辑:就是从系统发送缓冲区中不停的取出数据,
 * 然后调用串口发送接口发送,直到系统发送缓冲区中没有要发送的数据了就返回
 */
static INT  uartStartup (SIO_CHAN  *pSioChan)
{
    CHAR  cChar;

    while (!uartGetTxChar(pTxArg, &cChar)) {
        uartPutChar(cChar);
    }

    return  (ERROR_NONE);
}
/*  3. uartSioCbInstall SylixOS没有为BSP直接提供操作系统缓冲区的接口,
 * 而是在tty设备创建时将内核中的读写系统缓冲区的函数指针传入SIO驱动中的callbackInstall 接口,
 * 驱动需要自己保存这两个函数指针和其对应的参数,我们可以在驱动中定义全局变量用来保存   */

static INT (*uartGetTxChar)(PVOID  pArg, PCHAR  pcChar);
static INT (*uartPutRcvChar)(PVOID  pArg, CHAR  cChar);
static PVOID  pTxArg;
static PVOID  pRxArg;
/*
 * 在callback回调函数被调用的时候,通过以下方法保存内核中的读写函数指针:
 */

static INT  uartSioCbInstall (SIO_CHAN  *pSioChan,
                              INT  iCallbackType,
                              VX_SIO_CALLBACK  callbackRoute,
                              PVOID  pvCallbackArg)
{
    switch (iCallbackType) {
    case SIO_CALLBACK_GET_TX_CHAR:    /* 获取传输字符 */
        uartGetTxChar = (INT (*)(PVOID, PCHAR))callbackRoute;
        pTxArg = pvCallbackArg;

        break;

    case SIO_CALLBACK_PUT_RCV_CHAR:  /*  将接收数据放入终端缓冲区  */
        uartPutRcvChar = (INT (*)(PVOID, CHAR))callbackRoute;
        pRxArg = pvCallbackArg;

        break;

    default:   /* 回调出错 */
        _ErrorHandle(ENOSYS);
        return  (PX_ERROR);
    }

    return  (ERROR_NONE);
}

static SIO_DRV_FUNCS  uartSioDrvFunc = {
    .ioctl = uartSioIoctl,
    .txStartup = uartStartup,
    .callbackInstall = uartSioCbInstall,
};

SIO_CHAN  *uartSioChanCreate (VOID)
{
    uartSioChan.pDrvFuncs = &uartSioDrvFunc;

    return  &uartSioChan;
}

自此一个最简易的串口驱动我们就完成了,编译一下BSP_T3:
在这里插入图片描述


版权声明:本文为graceyun原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。