SylixOS BSP开发(十)

gewenbin
gewenbin
gewenbin
188
文章
15
评论
2021年4月3日22:03:37 6 3,385

实现串口SIO驱动(二)

在实现SIO驱动操作集之前,我们先来学习下SylixOS下标准输出、标准输入、标准出错时如何工作的:

SylixOS BSP开发(十)

  • 标准输出:当程序中使用printf打印信息时,就是往系统标准输出上输出信息。但是这些信息并不是立马就输出到串口上,而是首先发送到系统缓冲区,然后当满足一定条件时再将系统缓冲区中保存的数据通过SIO驱动中的发送接口操作硬件发送出去。一般这个触发条件是发送的数据中有换行符或者发送缓冲区满了。
  • 标准输入:类似于标准输出,当硬件收到数据时,先送到系统缓冲区,当满足一定条件时再将系统缓冲区的数据拷贝到程序的buffer中。这个触发条件一般是接收的数据中有回车符或者接收缓冲区满了。
  • 标准出错:不同于标准输入和输出都有系统缓冲区,标准出错需要立即将出错信息输出出来,所以没有系统缓冲区,直接通过SIO驱动进行数据发送输出。

这下我们再来看看SIO驱动中的三个操作集接口如何实现:

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

1. uartSioIoctl

这个函数需要实现SIO通道的打开、关闭、硬件参数设置、波特率设置等等,如下所示:

static INT  uartSioIoctl (SIO_CHAN  *pSioChan, INT  iCmd, PVOID  pArg)
{
    switch (iCmd) {
    case SIO_BAUD_SET:
        break;

    case SIO_BAUD_GET:
        *((LONG *)pArg) = 115200;
        break;

    case SIO_HW_OPTS_SET:
        break;

    case SIO_HW_OPTS_GET:
        *(LONG *)pArg = 0;
        break;

    case SIO_OPEN:
        break;

    case SIO_HUP:
        break;

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

    return  (ERROR_NONE);
}
  • SIO_BAUD_SET:波特率设置,需要根据应用层传入的波特率设置硬件。
  • SIO_BAUD_GET:获取当前设置的波特率,由于调试串口是U-Boot初始化的,波特率是115200,这里就直接返回。
  • SIO_HW_OPTS_SET:设置硬件参数,比如数据位数、奇偶校验位、停止位数等等。
  • SIO_HW_OPTS_GET:获取当前硬件设置的参数。
  • SIO_OPEN:打开SIO通道,一般这里会调用一些硬件的初始化代码。
  • SIO_HUP:关闭SIO通道,一般这里回收一些资源。

之前的教程中说了,为了让大家关注框架而不是硬件寄存器设置,所以这里大部分的命令对应的实现都直接返回了,应该说来是比较简单的。

2. 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);
}

这样在驱动中要从系统缓冲区中读取数据发送就可以调用uartGetTxChar 这个函数指针,要上送数据到系统缓冲区时可以使用uartPutRcvChar 这个函数指针。

3. uartStartup

接下来我们就需要实现SIO驱动中的发送函数了。这个接口的逻辑很简单,就是从系统发送缓冲区中不停的取出数据,然后调用串口发送接口发送,直到系统发送缓冲区中没有要发送的数据了就返回:

static INT  uartStartup (SIO_CHAN  *pSioChan)
{
    CHAR  cChar;

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

    return  (ERROR_NONE);
}

4. 数据接收

串口数据接收一般使用中断来实现,但是我们现在还没有编写好中断控制器的驱动代码,所以无法使用中断功能,串口的中断接收功能我们那就留到最后来实现。

到此为止我们就编写好了一个基本的串口SIO驱动,我们目前的实现时比较简单的,在实际的串口SIO驱动中我们还要考虑多通道、不同波特率和硬件参数设置、通过中断进行发送数据等等功能,这里为了快速地学习基础的框架,我们将上述功能狗省略了,这需要大家注意。

我们将编写好的sio.c同样放在uart目录下。

附源码:

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;

static INT  uartSioIoctl (SIO_CHAN  *pSioChan, INT  iCmd, PVOID  pArg)
{
    switch (iCmd) {
    case SIO_BAUD_SET:
        break;

    case SIO_BAUD_GET:
        *((LONG *)pArg) = 115200;
        break;

    case SIO_HW_OPTS_SET:
        break;

    case SIO_HW_OPTS_GET:
        *(LONG *)pArg = 0;
        break;

    case SIO_OPEN:
        break;

    case SIO_HUP:
        break;

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

    return  (ERROR_NONE);
}

static INT  uartStartup (SIO_CHAN  *pSioChan)
{
    CHAR  cChar;

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

    return  (ERROR_NONE);
}

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;
}

 

gewenbin
  • 本文由 发表于 2021年4月3日22:03:37
  • 转载请务必保留本文链接:http://www.databusworld.cn/10161.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

评论:6   其中:访客  4   博主  2
    • yhf yhf 4

      UART的接收不是通过中断来实现的么?

      • yhf yhf 4

        UART兼容16c550的话,是不是可以直接使用内核提供的16c550呢.

          • gewenbin gewenbin

            @ yhf 看实际硬件,有的可以直接用,用的得做一些修改

          • yhf yhf 4

            我觉得这里写的txStartup是不是更像是pollOutput的工作,一直不停循环读取数据

              • gewenbin gewenbin

                @ yhf 那是因为示例里使用的是轮询方式,实际也可以使用中断方式,txstartup只是要启动发送了,至于怎么发,看具体实现

              • liqingquan liqingquan 3

                写的挺好!