实现串口SIO驱动(二)
在实现SIO驱动操作集之前,我们先来学习下SylixOS下标准输出、标准输入、标准出错时如何工作的:
- 标准输出:当程序中使用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; }
2021年8月16日 14:54 1F
UART的接收不是通过中断来实现的么?
2021年8月16日 15:04 2F
UART兼容16c550的话,是不是可以直接使用内核提供的16c550呢.
2021年8月16日 20:58 B1
@ yhf 看实际硬件,有的可以直接用,用的得做一些修改
2021年8月16日 15:32 3F
我觉得这里写的txStartup是不是更像是pollOutput的工作,一直不停循环读取数据
2021年8月16日 20:57 B1
@ yhf 那是因为示例里使用的是轮询方式,实际也可以使用中断方式,txstartup只是要启动发送了,至于怎么发,看具体实现
2021年11月23日 18:53 4F
写的挺好!