# MS-RTOS UART 驱动开发

本章将介绍 MS-RTOS UART 驱动开发及测试。

UART 是通用异步收发器(Universal Asynchronous Receiver/Transmitter)的英文缩写,它包括了RS232 (opens new window)RS449 (opens new window)RS423 (opens new window)RS422 (opens new window)RS485 (opens new window)等接口标准规范和总线标准规范,即UART是异步串行通信口的总称。 —— 维基百科

# 1. 串口基础知识

# 1.1 硬件接口

串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,其通讯协议可分层为协议层和物理层。物理层规定通信协议中具有机械、电子功能的特性,从而确保原始数据在物理媒体的传播;协议层主要规定通讯逻辑,统一双方的数据打包、解包标准。根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。功能强大的串口具有相当丰富的资源,有分数波特率发生器、支持同步单线通信和半双工单线通讯、支持 LIN、支持调制解调器操作、智能卡协议和 IrDA SIR ENDEC 规范、具有 DMA 等。

80C51 串行口组成示意图:

uart_arch

RS232(DB9)接口示意图:

uart_RS232

# 1.2 通信参数

  • 波特率(Baud rate):

    用于衡量通信速度的参数,具体表示每秒钟传送的bit的个数。当提到时钟周期时,即指波特率。在通信过程中传输距离和波特率成反比。

  • 数据位(Data bits):

    用于衡量通信中实际数据位的参数,数据位的标准值有5/6/7/8位。数据位的具体设置取决于传送的信息类型。例如,对于标准的ASCII码是0~127,因此数据位可设为7位。扩展的ASCII码是0~255,则数据位设置为8位。

  • 停止位(Stop bit):

    用于表示单个包的最后一位,典型的值有1,1.5和2位。停止位不仅表示传输的结束,而且提供计算机校正时钟机会。因此停止位的位数越多,时钟同步的容忍程度越大,同时数据传输率也越慢。

  • 校验位(Parity bit):

    在串口通信中一种简单的检错方式。有四种检错方式:奇/偶校验以及高/低校验。对于奇和偶校验的情况,串口会对校验位进行设置,用该值确保传输的数据有奇数个或者偶数个逻辑高位。对于高/低校验位来说,高位和低位不是用来进行数据的检查,而是简单置位逻辑高或者逻辑低进行校验。

# 2. 串口驱动框架

# 2.1 驱动相关数据结构

(1)ms_io_device_t

/*
 * ms_io_device_t
 */
struct ms_io_device {
    ms_io_name_node_t       nnode;
    ms_io_driver_t         *drv;

    ms_ptr_t                ctx;
    ms_atomic_t             ref;
};

任何 IO 设备对象都应该采用包含的方式来继承 ms_io_device 对象。在实现 UART 驱动时,可以自定义一个设备结构体 uart_dev_t ,该结构体中包含一个 ms_io_device 类型的成员,还包含一个 privinfo_t 类型的成员;privinfo_t 由驱动开发人员定义,一般用于记录一些设备私有的状态信息和控制信息。

/*
 * Private Info
 */
typedef struct {
    ms_pollfd_t       *slots[1U];
    ms_ptr_t           fifo_buf;
    ms_fifo_t          rx_fifo;
    ms_fifo_t          tx_fifo;
    ms_handle_t        rx_semb;
    ms_handle_t        tx_semb;
} privinfo_t;

/*
 * UART Device
 */
typedef struct {
    privinfo_t      priv;
    ms_io_device_t  dev;
} uart_dev_t;

(2)ms_io_driver_t

/*
 * ms_io_driver_ops_t
 */
typedef struct {
    ms_io_drv_type_t type;

    int        (*open)  (ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode);
    int        (*close) (ms_ptr_t ctx, ms_io_file_t *file);
    ms_ssize_t (*read)  (ms_ptr_t ctx, ms_io_file_t *file, 
                         ms_ptr_t buf, ms_size_t len);
    ms_ssize_t (*write) (ms_ptr_t ctx, ms_io_file_t *file, 
                         ms_const_ptr_t buf, ms_size_t len);
    int        (*ioctl) (ms_ptr_t ctx, ms_io_file_t *file, int cmd, ms_ptr_t arg);
} const ms_io_driver_ops_t;

/*
 * ms_io_driver_t
 */
struct ms_io_driver {
    ms_io_name_node_t      nnode;
    ms_io_driver_ops_t    *ops;
};

任何 IO 设备驱动对象都应该采用包含的方式来继承 ms_io_driver 对象。ms_io_driver 结构体包含一个名字节点 ms_io_name_node_t 和一个设备文件操作接口集 ms_io_driver_ops_t 指针;名字节点用于匹配设备,文件操作接口用于支持 MS-RTOS IO 系统对设备的访问和控制。

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t uart_16c550_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __uart_open,
        .close  = __uart_close,
        .write  = __uart_write,
        .read   = __uart_read,
        .ioctl  = __uart_ioctl,
        .fstat  = __uart_fstat,
        .poll   = __uart_poll,
};

/*
 * Device driver
 */
static ms_io_driver_t uart_16c550_drv = {
        .nnode = {
            .name = "uart_16c550",
        },
        .ops = &uart_16c550_drv_ops,
};

# 2.2 驱动的注册和卸载

(1)串口驱动开发流程:

  • 获取必要的软硬件开发资源,了解设备的基本特性;

  • 参照手册的相关流程和代码规范,编写寄存器相关宏定义,封装通用硬件操作接口;

  • 申请必要的系统资源,根据默认参数初始化硬件的工作模式,实现数据发送、接收和中断处理;

  • 实现 ms_io_driver_ops_t 中的必要操作接口,并向 MS-RTOS IO 系统注册驱动和设备节点;

  • 检查代码质量和代码风格,编写测试程序;

  • 串口数据接收和发送;

(2)驱动的注册和卸载接口:

ms_err_t ms_io_driver_register(ms_io_driver_t *drv);
ms_err_t ms_io_driver_unregister(ms_io_driver_t *drv); //此接口暂不开放

(3)设备节点的注册和卸载接口:

/**
 * @brief Register a device.
 *
 * @param[in] dev               Pointer to device structure
 * @param[in] dev_path          The path of device
 * @param[in] drv_name          The name of driver
 * @param[in] ctx               Pointer to driver context
 *
 * @return Error number
 */
ms_err_t ms_io_device_register(ms_io_device_t *dev, const char *dev_path, const char *drv_name, ms_ptr_t ctx);

/**
 * @brief Unregister a device.
 *
 * @param[in] dev               Pointer to device
 *
 * @return Error number
 */
ms_err_t ms_io_device_unregister(ms_io_device_t *dev);

# 2.3 串口 ioctl 命令

以下仅列出几个最基本的命令,可以在 sdk/src/driver/ms_drv_uart.h 文件中找到所有命令的定义。

命令 描述 参数
MS_UART_CMD_SET_PARAM 设置串口的工作模式 ms_uart_param_t 指针
MS_UART_CMD_GET_PARAM 获取串口的工作模式 ms_uart_param_t 指针
MS_UART_CMD_FLUSH_RX 清除串口的发送缓冲区
MS_UART_CMD_DRAIN_TX 将串口发送缓冲区中的数据全部发送完
MS_UART_CMD_SET_R_TIMEOUT 设置读超时时间(单位 ms) ms_uint32_t 指针
MS_UART_CMD_GET_R_TIMEOUT 获取读超时时间(单位 ms) ms_uint32_t 指针

(1)ms_uart_param_t

typedef struct {
    ms_uint32_t     baud;
    ms_uint8_t      data_bits;
    ms_uint8_t      stop_bits;
    ms_uint8_t      parity;
    ms_uint8_t      flow_ctl;
    ms_uint8_t      mode;
    ms_uint8_t      clk_pol;
    ms_uint8_t      clk_pha;
    ms_uint8_t      clk_last_bit;
} ms_uart_param_t;
  • 串口传输时的数据位位数 (data_bits)
可选配置 描述
MS_UART_DATA_BITS_5B 串口每次传输5bit的数据 (不包括奇偶校验位)
MS_UART_DATA_BITS_6B 串口每次传输6bit的数据 (不包括奇偶校验位)
MS_UART_DATA_BITS_7B 串口每次传输7bit的数据 (不包括奇偶校验位)
MS_UART_DATA_BITS_8B 串口每次传输8bit的数据 (不包括奇偶校验位)
  • 串口传输时的停止位位数 (stop_bits)
可选配置 描述
MS_UART_STOP_BITS_1B 串口传输时使用1bit停止位
MS_UART_STOP_BITS_2B 串口传输时使用2bit停止位
MS_UART_STOP_BITS_0_5 串口传输时使用0.5bit停止位
MS_UART_STOP_BITS_1_5 串口传输时使用1.5bit停止位

停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。

  • 串口传输时的奇偶校验模式 (parity)
可选配置 描述
MS_UART_PARITY_NONE 串口传输时不使用奇偶校验位
MS_UART_PARITY_ODD 串口传输时使用奇校验位
MS_UART_PARITY_EVEN 串口传输时使用偶校验位
  • 串口传输时的流控模式(flow_ctl)
可选配置 描述
MS_UART_FLOW_CTL_NONE 串口传输时不使用硬件流控
MS_UART_FLOW_CTL_RTS 输出信号:发送请求信号,低电平有效
MS_UART_FLOW_CTL_CTS 输入信号:发送允许信号,低电平有效
MS_UART_FLOW_CTL_RTS_CTS 串口传输时使用硬件流控

RTS (Require ToSend,发送请求)为输出信号,用于指示本设备准备好可接收数据,低电平有效,低电平说明本设备可以接收数据。CTS (Clear ToSend,发送允许)为输入信号,用于判断是否可以向对方发送数据,低电平有效,低电平说明本设备可以向对方发送数据。

  • 串口传输时的传输模式 (mode)
可选配置 描述
MS_UART_MODE_RX 串口传输时仅使能接收功能
MS_UART_MODE_TX 串口传输时仅使能发送功能
MS_UART_MODE_TX_RX 串口传输时使能全双工模式
  • 串口传输时的时钟极性 (clk_pol)
可选配置 描述
MS_UART_CPOL_LOW 空闲时SCLK引脚为低电平
MS_UART_CPOL_HIGH 空闲时SCLK引脚为高电平

同步模式下SCLK引脚上输出时钟极性设置,可设置在空闲时SCLK引脚为低电平(USART_CPOL_Low)或高电 (USART_CPOL_High)。它设定USART_CR2寄存器的CPOL位的值。

  • 串口传输时的时钟相位 (clk_pha)
可选配置 描述
MS_UART_CPHA_1EDGE 在时钟第一个变化沿捕获数据
MS_UART_CPHA_2EDGE 在时钟第二个变化沿捕获数据

同步模式下SCLK引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定USART_CR2寄存器的CPHA位的值。USART_CPHA与USART_CPOL配合使用可以获得多种模式时钟关系。

  • 串口传输时的时钟最后一位(clk_last_bit)
可选配置 描述
MS_UART_LAST_BIT_DISABLE 发送最后一个数据位的时候时钟脉冲在SCLK引脚上不输出
MS_UART_LAST_BIT_ENABLE 发送最后一个数据位的时候时钟脉冲在SCLK引脚上输出

选择在发送最后一个数据位的时候时钟脉冲是否在SCLK引脚输出,可以是不输出脉冲(USART_LastBit_Disable)、输出脉冲(USART_LastBit_Enable)。它设定USART_CR2寄存器的LBCL位的值。

# 2.4 阻塞和非阻塞读写

应用程序在打开设备文件时,需要要指定读写权限;如果该设备文件为只读或者当前应用程序不具备写权限,则 open 操作将返回错误,应用程序可以通过 errno 获取到具体的错误码。

static int __xxx_dev_open(ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode)
{
    ms_atomic_inc(MS_IO_DEV_REF(file));

    return 0;
}

static int __xxx_dev_close(ms_ptr_t ctx, ms_io_file_t *file)
{
    ms_atomic_dec(MS_IO_DEV_REF(file));

    return 0;
}

应用程序在读设备文件时,对应到驱动中的 __xxx_dev_read 操作;在驱动中需要对 file->flags 进行检查,判断是阻塞读还是非阻塞读。相应的,应用程序在写数据到设备文件时,对应到驱动中的 __xxx_dev_write

static ms_ssize_t __xxx_dev_read(ms_ptr_t ctx, ms_io_file_t *file, 
                                 ms_ptr_t buf, ms_size_t len)
{
    privinfo_t *priv = ctx;
    ms_ssize_t ret;

    if ((file->flags & FREAD) != 0) {
        __XXX_DEV_LOCK(priv);
        while (remain_len > 0) {
            rlen = ms_fifo_get(&priv->rx_fifo, rbuf, remain_len);
            if (rlen > 0) {
				/* do something */
            } else {
                if (file->flags & FNONBLOCK) {
                    break;
                } else {
                    if (ms_cond_wait(priv->rx_fifo_r_condid, priv->lockid,
                                     MS_TIMEOUT_FOREVER) != MS_ERR_NONE) {
                        break;
                    }
                }
            }
        }
        __XXX_DEV_UNLOCK(priv);

    } else {
        ms_thread_set_errno(EACCES);
        ret = -1;
    }

    return ret;
}

# 2.5 串口 select 支持

应用程序在调用 select 接口来监听设备文件时,将触发以下调用流程,最终会调用设备驱动的 __ms_pty_poll 接口,并通过 __readable_check__writable_check__except_check 判断该设备是否触发了所监听的事件;如果设备已经触发了事件则立即返回,否则当前应用程序被挂起,知道事件到来或者超时。

uart_select

当输入、输出或异常事件发生,驱动程序调用 __xxx_dev_notify 来通知内核,内核将唤醒等待对应事件的应用程序。__xxx_dev_notify 的实现如下所示,实际上就是调用 ms_io_poll_notify_helper

/*
 * Device notify
 */
static int __uart_poll_notify(privinfo_t *priv, ms_pollevent_t event)
{
    return ms_io_poll_notify_helper(priv->slots, MS_ARRAY_SIZE(priv->slots), event);
}

/*
 * Poll device
 */
static int __uart_poll(ms_ptr_t ctx, ms_io_file_t *file, 
                       ms_pollfd_t *fds, ms_bool_t setup)
{
    privinfo_t *priv = ctx;

    return ms_io_poll_helper(fds, priv->slots, MS_ARRAY_SIZE(priv->slots), setup, ctx,
                             __uart_readable_check, 
                             __uart_writable_check, 
                             __uart_except_check);
}

# 2.6 串口驱动示例

UART 驱动示例,仅作为参考。

#define __MS_IO
#include "ms_config.h"
#include "ms_rtos.h"
#include "ms_io_core.h"
#include "includes.h"
#include "hardware.h"

/*
 * Open device
 */
static int __uart_open(ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode)
{
    privinfo_t *priv = ctx;
    int ret;

    if (ms_atomic_inc(MS_IO_DEV_REF(file)) == 1) {
        if (__uart_hw_init(priv) == MS_ERR_NONE) {
			ret = 0;
        } else {
            ms_atomic_dec(MS_IO_DEV_REF(file));
            ms_thread_set_errno(EINVAL);
            ret = -1;
        }
    } else {
        ms_atomic_dec(MS_IO_DEV_REF(file));
        ms_thread_set_errno(EBUSY);
        ret = -1;
    }

    return ret;
}

/*
 * Close device
 */
static int __uart_close(ms_ptr_t ctx, ms_io_file_t *file)
{
    privinfo_t *priv = ctx;

    if (ms_atomic_dec(MS_IO_DEV_REF(file)) == 0) {
        ms_fifo_reset(&priv->rx_fifo);
        ms_fifo_reset(&priv->tx_fifo);
    }

    return 0;
}

/*
 * Read device
 */
static ms_ssize_t __uart_read(ms_ptr_t ctx, ms_io_file_t *file, ms_ptr_t buf, ms_size_t len)
{
    privinfo_t *priv = ctx;
    ms_ssize_t ret = 0;
    ms_uint8_t *cbuf = buf;
    ms_uint32_t read_len;

    while (len > 0) {
		//TODO:
    }

    return ret;
}

/*
 * Write device
 */
static ms_ssize_t __uart_write(ms_ptr_t ctx, ms_io_file_t *file, ms_const_ptr_t buf, ms_size_t len)
{
    ms_arch_sr_t sr;
    privinfo_t *priv = ctx;
    ms_ssize_t ret = 0;
    const ms_uint8_t *cbuf = buf;
    ms_uint32_t write_len;

    while (len > 0) {
		//TODO: 
    }

    return ret;
}

/*
 * Control device
 */
static int __uart_ioctl(ms_ptr_t ctx, ms_io_file_t *file, int cmd, void *arg)
{
    ms_arch_sr_t sr;
    privinfo_t *priv = ctx;
    ms_err_t err;
    int ret = 0;

    switch (cmd) {
    case MS_UART_CMD_GET_PARAM:
        if (ms_access_ok(arg, sizeof(ms_uart_param_t), MS_ACCESS_W)) {
			//TODO:
        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    case MS_UART_CMD_SET_PARAM:
        if (ms_access_ok(arg, sizeof(ms_uart_param_t), MS_ACCESS_R)) {
			//TODO:
        } else {
            ms_thread_set_errno(EFAULT);
            ret = -1;
        }
        break;

    default:
        ms_thread_set_errno(EINVAL);
        ret = -1;
        break;
    }

    return ret;
}

/*
 * Check device readable
 */
static ms_bool_t __uart_readable_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;

    return ms_fifo_is_empty(&priv->rx_fifo) ? MS_FALSE : MS_TRUE;
}

/*
 * Check device writable
 */
static ms_bool_t __uart_writable_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;

    return ms_fifo_is_full(&priv->tx_fifo) ? MS_FALSE : MS_TRUE;
}

/*
 * Check device exception
 */
static ms_bool_t __uart_except_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;

    (void)priv;

    return MS_FALSE;
}

/*
 * Device notify
 */
static int __uart_poll_notify(privinfo_t *priv, ms_pollevent_t event)
{
    return ms_io_poll_notify_helper(priv->slots, MS_ARRAY_SIZE(priv->slots), event);
}

/*
 * Poll device
 */
static int __uart_poll(ms_ptr_t ctx, ms_io_file_t *file, ms_pollfd_t *fds, ms_bool_t setup)
{
    privinfo_t *priv = ctx;

    return ms_io_poll_helper(fds, priv->slots, MS_ARRAY_SIZE(priv->slots), setup, ctx,
                             __uart_readable_check, __uart_writable_check, __uart_except_check);
}

/*
 * UART ISR
 */
static inline void __uart_isr(ms_uint8_t channel)
{
    uart_dev_t  *dev;

    dev = __uart_devs[channel - 1];

    //TODO: 
}

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t uart_16c550_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __uart_open,
        .close  = __uart_close,
        .write  = __uart_write,
        .read   = __uart_read,
        .ioctl  = __uart_ioctl,
        .poll   = __uart_poll,
};

/*
 * Device driver
 */
static ms_io_driver_t uart_16c550_drv = {
        .nnode = {
            .name = "uart_16c550",
        },
        .ops = &uart_16c550_drv_ops,
};

/*
 * Register UART device driver
 */
ms_err_t uart_drv_register(void)
{
    return ms_io_driver_register(&uart_16c550_drv);
}

/*
 * Create UART device file
 */
ms_err_t uart_dev_create(const char *dev_path, uart_channel_t *channel)
{
    uart_dev_t  *dev;
    privinfo_t  *priv;
	
	//TODO: 

    ms_fifo_init(&priv->rx_fifo, (ms_uint8_t *)priv->fifo_buf, rx_buf_size);
    ms_fifo_init(&priv->tx_fifo, (ms_uint8_t *)priv->fifo_buf + rx_buf_size, tx_buf_size);

    err = ms_io_device_register(&dev->dev, dev_path, "uart_16c550", priv);
    if (err != MS_ERR_NONE) {
        goto err_device_register;
    }

    return MS_ERR_NONE;
}

# 3. 串口应用程序

# 3.1 串口实现 echo 功能

打开 UART 设备文件,实现串口回射信息的功能:

#include <ms_rtos.h>
#include <string.h>
#include <driver/ms_drv_uart.h>

#define UART_DEVICE_PATH    "/dev/BLE_UART"
#define UART_TEST_TIMES     (2)

static ms_uint32_t receive_count;
static ms_uint8_t  receive_buffer[256];
static ms_uint8_t  transmit_buffer[256];
static ms_uint8_t  transmit_data[16] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, \
                                        0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xe, 0xf};

int main(void)
{
    int              fd;
    int              ret;
    int              index;
    ms_uint32_t      length;
    ms_uint8_t      *buffer;
    ms_uint32_t      buffer_len;
    ms_uart_param_t  param;
    ms_uint32_t      test_count = UART_TEST_TIMES;

    buffer = (ms_uint8_t*)transmit_data;
    buffer_len = sizeof(transmit_data);

    fd = ms_io_open(UART_DEVICE_PATH, O_RDWR | O_NONBLOCK, 0);
    if (fd < 0) {
        ms_printf("[error]: open device file %s failed!\n", UART_DEVICE_PATH);
        return -1;
    }

    ret = ms_io_ioctl(fd, MS_UART_CMD_GET_PARAM, &param);
    if (ret < 0) {
        ms_printf("[error]: get uart param failed!\n");
        ms_io_close(fd);
        return -1;
    }

    param.baud      = 115200;
    param.data_bits = MS_UART_DATA_BITS_8B;
    param.stop_bits = MS_UART_STOP_BITS_1B;
    param.parity    = MS_UART_PARITY_NONE;
    ret = ms_io_ioctl(fd, MS_UART_CMD_SET_PARAM, &param);
    if (ret < 0) {
        ms_printf("[error]: set uart param failed!\n");
        ms_io_close(fd);
        return -1;
    }

    while (test_count--) {
        ret = ms_io_read(fd, receive_buffer, sizeof(receive_buffer));
        if (ret > 0) {
            length = ret;
            receive_count += length;

            ms_printf("[read msg: len=%d, total=%d]:", length, receive_count);
            for (index = 0; index < length; index++) {
                ms_printf(" 0x%02x", receive_buffer[index]);
            }
            ms_printf("\n");

            ret = ms_io_write(fd, receive_buffer, length);
            if (ret < 0) {
                ms_printf("[error]: echo failed! ret = %d\n", ret);
            }

        } else {
            ms_printf("[info]: no message receive, then send testing data\n");
            ret = ms_io_write(fd, buffer, buffer_len);
            if (ret < 0) {
                ms_printf("[error]: send msg failed! ret = %d\n", ret);
            }
        }

        ms_thread_sleep_ms(10);
    }

    ms_io_close(fd);

    return 0;
}

# 3.2 使用 select 监听串口

打开 UART 设备文件,监听串口数据:

#include <ms_rtos.h>
#include <string.h>
#include <driver/ms_drv_uart.h>
#include "test/include/greatest.h"

#define UART_DEVICE_PATH    "/dev/BLE_UART"
#define UART_TEST_TIMES     (2)

static ms_uint32_t receive_count;
static ms_uint8_t  receive_buffer[256];
static ms_uint8_t  transmit_buffer[256];
static ms_uint8_t  transmit_data[16] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, \
                                        0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xe, 0xf};

int test_uart_select(void)
{
    int             ret;
    int             index;
    int             fd_uart;
    fd_set          read_fd_set;
    struct timeval  timeout;
    ms_uint32_t     test_count = UART_TEST_TIMES;

    fd_uart = ms_io_open(UART_DEVICE_PATH, O_RDONLY | O_NONBLOCK, 0666);
    if (fd_uart < 0) {
        ms_printf("[error]: open device file %s failed!\n", UART_DEVICE_PATH);
        return -1;
    }

    memset(transmit_buffer, 0, sizeof(transmit_buffer));

    while (test_count--) {
        timeout.tv_sec = 3;
        timeout.tv_usec = 0;
        FD_ZERO(&read_fd_set);
        FD_SET(fd_uart, &read_fd_set);

        ret = select(fd_uart+1, &read_fd_set, NULL, NULL, &timeout);
        if (ret < 0) {
            ms_printf("[error]: something error in select!\n");

        } else if (ret) {
            if (FD_ISSET(fd_uart, &read_fd_set)) {
                ret = ms_io_read(fd_uart, receive_buffer, sizeof(receive_buffer));
                if (ret > 0) {
                    receive_count += ret;

                    ms_printf("\n[read msg: len=%d total=%d]:", ret, receive_count);
                    for (index = 0; index < ret; index++) {
                        ms_printf(" 0x%02x", receive_buffer[index]);
                    }
                    ms_printf("\n");

                } else {
                    ms_printf("[error]: fd is set but reading failed!\n");
                }
            }
        } else {
            ms_printf("[info]: A timeout occurred during the execution of select!\n");
        }
    }

    ms_io_close(fd_uart);

    return 0;
}

# 附录(Appendix)

1. Reference

STM32 串口通讯:https://blog.csdn.net/qq_43743762/article/details/97811470

2. FAQ

(1)如何自定义串口的 ioctl 命令?

在串口驱动头文件中定义新的 ioctl 命令,在串口驱动中实现对应的命令;将串口驱动头文件添加到应用程序工程源码中,使用自定义的命令来调用 ioctl 系统调用。