zynq操作系统: Linux驱动开发串口篇

简介: 串口( UART)是一种非常常见的外设, 串口在嵌入式开发领域当中一般作为一种调试手段,通过串口将调试信息打印出来,或者通过串口发送指令给主机端进行处理;当然除了作为基本的调试手段之外,还可以通过串口与其他设备或传感器进行通信, 譬如有些 sensor 就使用了串口通信的方式与主机端进行数据交互。

一.简介

 串口( UART)是一种非常常见的外设, 串口在嵌入式开发领域当中一般作为一种调试手段,通过串口将调试信息打印出来,或者通过串口发送指令给主机端进行处理;当然除了作为基本的调试手段之外,还可以通过串口与其他设备或传感器进行通信, 譬如有些 sensor 就使用了串口通信的方式与主机端进行数据交互。

根据电平标准的不同,串口可以分为 TTL, RS232,RS485, RS422等这些,虽然它们的电平标准不同,但是却都遵循相同的通信时序协议,所以呢,不管它是什么样的电平标准,对于我们软件而言其驱动程序都是一样的;一般主机端直接出来的电平信号就是 TTL

对于 ZYNQ 来说,其 PS 端只提供了两个串口外设 UART0 和 UART1,很明显对于大一点的工程而言是不够用的,此时可以使用 PL 端串口软核外设, xilinx 同样提供了相应的 IP核 可供调用如这次使用的UART16550

二.基础知识准备

 串口在嵌入式 Linux 系统当中经常作为系统的标准输入、输出设备,而提到串口,那么就不得不引出另外两个概念:终端与控制台。关于这两个概念,很多人估计是对此傻傻分不清!

 这里参考了原子文档里对终端的理解

 1、 什么是终端 Terminal

 终端就是处理主机输入、 输出的一套设备,它用来显示主机运算的输出,并且接受主机要求的输入。 典型的终端包括显示器键盘套件,打印机打字机套件等。 其实本质上也就一句话,能接受输入、能显示输出,这就够了,不管到了什么时代,终端始终扮演着人机接口的角色, 所谓 Terminal,即机器的边缘!

只要能提供给计算机输入和输出功能,它就是终端,而与其所在的位置无关。

 2、终端的分类

 ⚫ 本地终端:例如对于我们的个人 PC 机来说, PC 机连接了显示器、键盘以及鼠标等设备, 这样的一个显示器/键盘组合就是一个本地终端;同样对于开发板来说也是如此,开发板也可以连接一个LCD 显示器、键盘和鼠标等,同样可以构成本地终端。

 ⚫ 用串口连接的远程终端:对于嵌入式 Linux 开发来说,这是最常见的终端—串口终端。 譬如我们的开发板通过串口线连接到一个带有显示器和键盘的 PC 机, 在 PC 机通过运行一个终端模拟程序,譬如 Windows 超级终端、 putty、 MobaXterm、 SecureCRT 等来获取并显示开发板通过串口发出的数据、同样还可以通过这些终端模拟程序将用户数据通过串口线发送给开发板。

 ⚫ 基于网络的远程终端:譬如我们可以通过 ssh、 Telnet 这些协议登录到一个远程主机。以上列举的这些都是终端,前两类称之为物理终端; 最后一个称之为伪终端。 前两类都是在本地就直接关联了物理设备的, 譬如显示器、鼠标键盘、 串口等之类的,这种终端叫做物理终端,而第三类在本地则没有关联任何物理设备,注意,不要把物理网卡当成终端关联的物理设备,它们与终端并不直接相关,所以这类不直接关联物理设备的终端叫做伪终端。

 3、什么是控制台 Console

能够显示系统信息的终端就叫控制台。 控制台的概念与终端含义非常相近,其实现在我们经常用它们表示相同的东西, linux 中基本也已经淡化了控制台和终端的区别。虽然说它们之间的含义非常相近,但还是有一些区别:

 ⚫ 能够显示系统信息的终端就叫控制台,这说明它们之间是一个包含关系,控制台一定是终端,而终端则不一定是控制台,也就是说控制台是终端的真子集。

 ⚫ 控制台只有一个。看到这里大家可能就有疑问了,我们使用的开发板可以通过串口终端打印信息,同样我们也可以通过 ssh 协议登录连接到开发板,同样也会打开一个伪终端,并且也可以在伪终端显示打印信息,那么它俩不都可以认为是控制台吗?其实并非如此,上面说到的显示系统信息指的是开发板启动的时候,所有的打印系统信息都会显示到这个终端上,那么这个终端才叫做控制台,所有由此可以知道,譬如我们的开发板在启动时,所有的打印信息都会通过串口输出,所以我们的串口终端就是控制台, 而通过 ssh 远程登录开发板打开的伪终端并不是控制台,因为启动时的打印信息是不可能输出到这个伪终端上的。

 ⚫ 控制台是计算机本身的设备,一个计算机只有一个控制台。譬如开发板的串口这就是开发板本身的设备。

讲到这里相信大家都应该清楚了,它们之间的共同点和小小的区别,其实我们也不用去刻意区分它们之间的异同,因为 Linux 中它们之间的区别基本完全淡化了。

 4、 Linux 下终端对应的设备文件

在 Linux 当中,一切皆是文件。当然,终端也不例外,每一个终端设备在/dev 目录下都

有一个对应的设备文件。

 ⚫ /dev/ttyX 设备文件: tty( teletype 的简称) 是最令人熟悉的了,在 Linux 中, /dev/ttyX 代表的都是上述的物理终端,其中, /dev/tty1~/dev/tty63 代表的是本地终端,也就是接到本机的键盘显示器可以操作的终端。事实上 Linux 内核在初始化时会生成 63 个本地终端。

 ⚫ /dev/console 设备文件:这个设备文件表示当前系统的控制台,如果我们往这个设备文件输入信息,它一定会显示在我们的控制台终端中,譬如“echo Hello > /dev/console”。

 ⚫ 串口终端 ttyPSX:对于我们的调试版来说,有一个ps的串口,12个PL的串口所以也对应了13串口终端设备文件,如下所示:

【图片】

当然这里的名字 ttyPS0,ttyS0不是固定的,这个具体的命名跟串口的驱动程序有关,名字不是统一的,但是名字前缀一般都以“ tty”开头,以表明它是一个终端设备

三.Linux下的串口调用

3.1介绍

 串口的工作原理肯定是跟之前裸机中介绍到的谁一样的,重点在编程时linux对串口不同的调用方式

 Linux 系统中 UART 串口驱动框架结构图如下所示

20210412111043555.png

 简单地说可以分为两层: UART 驱动层和 tty 驱动层。 从图中可以看到,下层 UART 驱动层直接与硬件相接触,也就是说它才是真正的 UART驱动程序,它提供了 UART 硬件操作相关函数集 uart_ops;而上层 tty 驱动层则会将 UART 设备描述成一个 tty(终端)设备, 并向内核注册 tty 字符设备,提供字符设备操作函数集ops,其实 ops 函数集中经过层层跳转最终执行的就是 uart 驱动层的 uart_ops。

首先对于驱动开发工程师来说, tty 驱动层并不需要我们去实现,它会在我们注册 UART

驱动的过程中自动构建出来,对于我们所使用的内核源码来说, xilinx 官方已经提供了串口驱动程序,所以只需要在应用层封装一层驱动了(真实令人感到偷懒和开心的消息)

(如果需要自己写的话,自行查找原子的免费开发文档吧,有很详细的讲解)

3.2 命令行调用测试

 老规矩,还是先使用命令行对串口进行一个简单的测试吧

第一步先要看我们有没有生成的tty串口设备,不确定是xilinx的默认设置还是怎么的,不论是交叉编译menuconfig还是官方编译的config kernel都对最大支持设备数有限制,我这边独到的最大值为4,所以需要改,配置内核时找到如下路径,对下面的最大支持设备改为自己的设备数,(没用官方推荐模式的可能默认连8250的设备树都没有支持,往下翻,找到并勾选)

.config - Linux/arm 4.14.0 Kernel Configuration
→Device Drivers →Character devices →Serial drivers -
Serial drivers
Arrow keys navigate the menu. <Enter> selects submenus ---> (or empty submenus----). Highlighted letters are hotkeys. Pressing <Y>
includes.<N> excludes. <M> modularizes features. press <Esc><Esc> to exit,<?> for Help,</> for Search. Legend: [*] built-in [ ]
<*>8250/16550 and compatible serial support
「*  Support 8250 core.* kernel options (DEPRECATED) 
Support for Fintek F81216A LPC t0 4 UART RS485 API Console on 8250/16550 and compatible serial port
[*] DMA support for 16550 compatible UART controllers 
<*> 8250/16550 PCI device support 
<*> 8250/16550 Exar/Commtech PcI/PCIe device support  
(12)  Maximum number of 8250 16550 serial ports 
(12)  Number of 8250/16550 serial ports to register at runtime  
Extended 8250/16550 serial driver options((+)
<Select>  Exit >  < Help >  Save >  < Load >  
Hx--1024

 stty查看串口参数

stty -F /dev/ttyS0 -a

 查看串口1(/dev/ttyS0)当前的参数,包括波特率、数据位等。

 stty设置串口参数

stty -F /dev/ttyS0 ispeed 115200 ospeed 115200 cs8

 该命令将串口1(/dev/ttyS0)设置成115200波特率,8位数据模式。一般情况下设置这两个参数就可以了,如果显示数据乱码,可能还需要设置其它参数,使用man  查看stty其它设置选项

 cat打印串口数据

cat /dev/ttyS0

 串口数据就可以在终端上显示了。

 发送数据

echo “HelloWorld” >/dev/ttyS0

3.3测试代码

然后就是编写代码了,下面是增改可用的串口驱动头文件:

#ifndef _SERIAL_H_
#define _SERIAL_H_
#include<stdio.h>      /*Standard input/output definitions*/    
#include<stdlib.h>     /*Standard function library definitions*/    
#include<unistd.h>     /*Unix Standard function definition*/    
#include<sys/types.h>     
#include<sys/stat.h>       
#include<fcntl.h>      /*File control definition*/    
#include<termios.h>    /*PPSIX Terminal control definition*/    
#include<errno.h>      /*Error number definition*/    
#include<string.h>
#include<sys/time.h>
#define FALSE  -1    
#define TRUE   0  
#define   B76800  0010020
#define   B153600 0010021
#define   B307200 0010022
#define   B614400 0010023
//
/*******************************************************************  
* name:           serial_open  
* function:       Opens the serial port and returns the serial device file description     
* @param[in]:     port :serial number(ttyS0,ttyS1)  
* @return:        Correct returns fd, error returns -1  
*******************************************************************/  
int serial_open(char* port);
/*******************************************************************  
* name:           serial_init()  
* function:       serial_init  
* @param[in]:     fd     :   File descriptor    
* @param[in]       speed  :   Serial speed  
* @param[in]       flow_ctrl  Data flow control  
* @param[in]       databits   The data bits , either 7 or 8  
* @param[in]       stopbits   The stop bit , either 1 or 2  
* @param[in]       parity     The value of effect type ,choose N,E,O, S  
*                        
* return:        Correct returns 0, error returns -1  
*******************************************************************/    
int serial_init(int fd, int speed,int flow_ctrl,int databits,int stopbits,int parity);
/*******************************************************************  
* name:           serial_recv  
* function:       Receive serial data  
* @param[in]:     fd:          File descriptor     
*                 rcv_buf   :  The data from the receiving serial port is stored in the rcv_buf buffer 
*                 data_len  :  The length of the data to read 
*                               
* return:        The actual length of the data read  
*******************************************************************/    
int serial_recv(int fd, char *rcv_buf,int data_len);
/********************************************************************  
* name:           serial_send  
* function:       To send data  
* @param[in]:     fd  :        File descriptor      
*                 send_buf    :Stores serial port to send data  
*                 data_len    :The length of the data to send  
* return:        Correct returns 0, error returns -1 
*******************************************************************/    
int serial_send(int fd, char *send_buf,int data_len);
/*******************************************************************  
* name:           serial_set  
* function:       Set serial port data bit, stop bit and effect check bit  
* @param[in]:   fd            File descriptor  
*                speed         serial speed  
*                flow_ctrl     Data flow control  
*                databits      The data bits , either 7 or 8  
*                topbits       The stop bit , either 1 or 2  
*                parity        The value of effect type ,choose N,E,O, S 
*return:         Correct returns 0, error returns -1  
*******************************************************************/  
int serial_set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity);
/*******************************************************************  
* name:           serial_close  
* function:       Closes the serial port and returns the serial device file description  
* @param[in]:     fd    :File descriptor     
* @param[in]:     port :serial number(ttyS0,ttyS1)  
* @return:        void  
*******************************************************************/  
void serial_close(int fd);
#endif

再然后是包含的库文件

#ifndef _PACKET_H
#define _PACKET_H
#include <stdint.h>
// #define DEBUG
#ifdef DEBUG
#define PRINT(fmt, ...)   printf(fmt, ##__VA_ARGS__)
#else
#define PRINT(fmt, ...)
#endif
typedef enum {
    UART_SND_REQ            = 0x11,
    UART_UPLOAD             = 0x22,
    UART_ATTR_SET_REQ       = 0x33,
    UART_ATTR_GET_REQ       = 0x44,
    UART_ATTR_GET_ACK       = 0x44,
    JTAG_CONNECT_SET_REQ    = 0x55,
    JTAG_CONNECT_GET_REQ    = 0x66,
    JTAG_CONNECT_GET_ACK    = 0x66,
    IP_ADDR_SET_REQ         = 0x77,
    IP_ADDR_GET_REQ         = 0x71,
    IP_ADDR_GET_ACK         = 0x71,
    SYSTEM_RESET            = 0x72
} MANAGE_TYPE;
typedef struct {
  uint32_t    PackageLen;
  uint8_t     ManageType;
} __attribute__((__packed__)) HEADER;
typedef struct {
  HEADER      header;
    uint8_t  PortID;
    uint8_t     Data[];
} __attribute__((__packed__)) UART_SND;
typedef struct {
  HEADER      header;
    uint8_t  PortID;
  uint8_t  Baudrate;
  uint8_t     DataBit;
    uint8_t     StopBit;
    uint8_t     Pirity;
    uint8_t     FlowCtrl;
  uint8_t     CheckSum;
} __attribute__((__packed__)) UART_SET_REQ;
typedef struct {
  HEADER      header;
    uint8_t  PortID;
  uint8_t     CheckSum;
} __attribute__((__packed__)) UART_GET_REQ;
#define UART_GET_ACK UART_SET_REQ
#define JTAG_SET_REQ UART_GET_REQ
#define JTAG_INQ_REQ UART_GET_REQ   //Jtag connect status inquire request
#define JTAG_INQ_ACK UART_GET_REQ   //Jtag connect status inquire answer
#define IP_INQ_REQ   UART_GET_REQ
#define COMMON_ACK   UART_GET_REQ
#define IP_INQ_ACK   IP_SET_REQ
typedef struct {
  HEADER      header;
  uint8_t  IpAddr[4];
    uint8_t  NetMask[4];
    uint8_t  GateWay[4];
    uint8_t  CheckSum;
} __attribute__((__packed__)) IP_SET_REQ;
typedef struct {
    int sock;
    char status;
    uint8_t ip[20];
} TCP_CLIENT;
#endif
之后就是驱动c文件了
#include "serial.h"
#include "packet.h"
/*******************************************************************  
* 名称:           serial_open  
* 功能:           打开串口并返回串口设备文件描述  
* 入口参数:        fd    :文件描述符     port :串口号(ttyS0,ttyS1)  
* 出口参数:        正确返回为fd,错误返回为-1  
*******************************************************************/    
int serial_open(char* port)    
{    
    int fd;
    fd = open( port, O_RDWR|O_NOCTTY);
    // fd = open( port, O_RDWR|O_NOCTTY|O_NDELAY);
    if (FALSE == fd)    
    {    
        perror("Can't Open Serial Port");    
        return(FALSE);    
    }    
    //恢复串口为阻塞状态                                   
    // if(fcntl(fd, F_SETFL, 0) < 0)    
    // {    
    //     PRINT("fcntl failed!\n");    
    //     return(FALSE);    
    // }         
    // else    
    // {    
    //     // PRINT("fcntl=%d\n",fcntl(fd, F_SETFL,0));    
    // }    
    // // //测试是否为终端设备        
    // // if(0 == isatty(STDIN_FILENO))    
    // // {    
    // //     PRINT("standard input is not a terminal device\n");    
    // //     return(FALSE);    
    // // }    
    // // else    
    // // {    
    // //     // PRINT("%s is a terminal device!\n", port);    
    // // }                  
    return fd;    
}    
/*******************************************************************  
* 名称:           serial_close  
* 功能:           关闭串口并返回串口设备文件描述  
* 入口参数:        fd    :文件描述符     port :串口号(ttyS0,ttyS1)  
* 出口参数:        void  
*******************************************************************/    
void serial_close(int fd)    
{    
    close(fd);    
}    
/*******************************************************************  
* 名称:           serial_set  
* 功能:           设置串口数据位,停止位和效验位  
* 入口参数:        fd        串口文件描述符  
*                 speed     串口速度  
*                 flow_ctrl   数据流控制  
*                 databits   数据位   取值为 7 或者8  
*                 topbits   停止位   取值为 1 或者2  
*                 parity     效验类型 取值为N,E,O,,S  
*出口参数:         正确返回为1,错误返回为0  
*******************************************************************/    
int serial_set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity)    
{    
    int   i;    
    //int   status;    
    int   speed_arr[] = { B115200, B38400, B19200, B9600, B4800, B2400, B1200, B614400,B76800};    
    //int   name_arr[] = {115200, 38400, 19200,  9600,  4800,  2400,  1200,  300};  
    // int   name_arr[] = {4,  3, 2,  1,  0,  2400,  1200,  300};   
    int   name_arr[] = {115200, 38400, 19200,  9600,  4800,  2400,  1200,  614400,76800};
    struct termios options;
    /*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1.  
    */    
    if( tcgetattr( fd,&options)  !=  0)    
    {    
        perror("SetupSerial 1");        
        return(FALSE);     
    }    
    //设置串口输入波特率和输出波特率    
    for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++)    
    {    
        if  (speed == name_arr[i])    
        {                 
            cfsetispeed(&options, speed_arr[i]);   
            // printf("ispeed is %d----------------------------\n",speed_arr[i]);  
            cfsetospeed(&options, speed_arr[i]);   
            // printf("ospeed is %d----------------------------\n",speed_arr[i]);     
        }    
    }         
    //修改控制模式,保证程序不会占用串口    
    options.c_cflag |= CLOCAL;    
    //修改控制模式,使得能够从串口中读取输入数据    
    options.c_cflag |= CREAD;
    //清bit位 关闭字符映射 0x0a 0x0d
    options.c_iflag &= ~(INLCR|ICRNL);
    //清bit位 关闭流控字符 0x11 0x13
    options.c_iflag &= ~(IXON);
    options.c_cflag |= CBAUDEX; //设置特定波特率的标志位.
    //设置数据流控制    
    switch(flow_ctrl)    
    {    
        case 0 ://不使用流控制    
              options.c_cflag &= ~CRTSCTS;    
              break;       
        case 1 ://使用硬件流控制    
              options.c_cflag |= CRTSCTS;    
              break;
        case 2 ://使用硬件流控制DTR/DSR
              options.c_cflag &= ~CRTSCTS;
              break;
        case 3 ://使用软件流控制    
              options.c_cflag |= IXON | IXOFF | IXANY;    
              break;    
    }    
    //设置数据位    
    //屏蔽其他标志位    
    options.c_cflag &= ~CSIZE;    
    switch (databits)    
    {      
        case 5    :    
                     options.c_cflag |= CS5;    
                     break;    
        case 6    :    
                     options.c_cflag |= CS6;    
                     break;    
        case 7    :        
                 options.c_cflag |= CS7;    
                 break;    
        case 8:        
                 options.c_cflag |= CS8;    
                 break;      
        default:       
                 fprintf(stderr,"Unsupported data size\n");    
                 return (FALSE);     
    }    
    //设置校验位    
    switch (parity)    
    {      
        case 0:    
        case 'N': //无奇偶校验位。    
                 options.c_cflag &= ~PARENB;     
                 options.c_iflag &= ~INPCK;        
                 break;     
        case 1:      
        case 'O'://设置为奇校验        
                 options.c_cflag |= (PARODD | PARENB);     
                 options.c_iflag |= INPCK;                 
                 break;     
        case 2:     
        case 'E'://设置为偶校验      
                 options.c_cflag |= PARENB;           
                 options.c_cflag &= ~PARODD;           
                 options.c_iflag |= INPCK;          
                 break;    
        case 's':    
        case 'S': //设置为空格     
                 options.c_cflag &= ~PARENB;    
                 options.c_cflag &= ~CSTOPB;    
                 break;     
        default:      
                 fprintf(stderr,"Unsupported parity\n");        
                 return (FALSE);     
    }     
    // 设置停止位     
    switch (stopbits)    
    {      
        case 1:       
                 options.c_cflag &= ~CSTOPB; break;     
        case 2:       
                 options.c_cflag |= CSTOPB; break;    
        default:       
                       fprintf(stderr,"Unsupported stop bits\n");     
                       return (FALSE);    
    }    
    //修改输出模式,原始数据输出    
    options.c_oflag &= ~OPOST;    
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);    
    //options.c_lflag &= ~(ISIG | ICANON);    
    //设置等待时间和最小接收字符    
    options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */      
    options.c_cc[VMIN] = 32; /* 读取字符的最少个数为1 */    
    //如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读    
    tcflush(fd,TCIFLUSH);    
    //激活配置 (将修改后的termios数据设置到串口中)    
    if (tcsetattr(fd,TCSANOW,&options) != 0)      
    {    
        perror("com set error!\n");      
        return (FALSE);     
    }    
    return (TRUE);     
}    
/*******************************************************************  
* 名称:         serial_init()  
* 功能:         串口初始化  
* 入口参数:      fd       :  文件描述符     
*               speed  :  串口速度  
*               flow_ctrl  数据流控制  
*               databits   数据位   取值为 7 或者8  
*               stopbits   停止位   取值为 1 或者2  
*               parity     效验类型 取值为N,E,O,,S  
*                        
* 出口参数:        正确返回为0,错误返回为-1  
*******************************************************************/    
int serial_init(int fd, int speed, int flow_ctrl, int databits, int stopbits, int parity)    
{       
    //设置串口数据帧格式    
    //if (serial_set(fd,19200,0,8,1,'N') == FALSE)
    if((speed < 0) || (speed > 10000000))
        return 1;
    if (serial_set(fd,speed,flow_ctrl,databits,stopbits,parity) == FALSE)
    {
        return FALSE;
    }
    else    
    {    
        return  TRUE;    
    }    
}    
/*******************************************************************  
* 名称:            serial_recv  
* 功能:            接收串口数据  
* 入口参数:         fd       :  文件描述符     
*                  rcv_buf   :接收串口中数据存入rcv_buf缓冲区中  
*                  data_len   :数据的长度 
*                               
* 出口参数:        实际读到的长度  
*******************************************************************/    
int serial_recv(int fd, char *rcv_buf,int data_len)  
{  
    int len,fs_sel;  
    fd_set fs_read;  
    struct timeval time;  
    FD_ZERO(&fs_read);  
    FD_SET(fd,&fs_read);  
    time.tv_sec = 10;  
    time.tv_usec = 0;  
  len = read(fd,rcv_buf,data_len);  
        // printf("I am right!(version1.2) len = %d fs_sel = %d\n",len,fs_sel);  
    return len;  
    //使用select实现串口的多路通信  
  /* 入口参数:
      ①:ndfs:select() 中监视的文件句柄,一般设为要监视的文件中的最大文件号加一。
      ②:rdfds:select()监视的可读文件句柄集合,当rdfds映象的文件句柄状态变成可读时系统告诉select函数返回。这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的读变化;
      ③:wtfds: select()监视的可写文件句柄集合,当wtfds映象的文件句柄状态变成可写时系统告诉select函数返回。
        如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,
        如果没有可写的文件,则根据timeout参数再判断是否超时,
        若超出timeout的时间,select返回0,若发生错误返回负值,
        可以传入NULL值,表示不关心任何文件的写变化。
      ④:exfds:select()监视的异常文件句柄集合,当exfds映象的文件句柄上有特殊情况发生时系统会告诉select函数返回。
      ⑤:timeout:select()的超时结束时间 */
   /* FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。
         FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。
         FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。
         FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。
 */
    // fs_sel = select(fd+1,&fs_read,NULL,NULL,&time);  
    // // printf("fs_sel = %d\n",fs_sel);  
    // if(fs_sel)  
    // {  
    //     len = read(fd,rcv_buf,data_len);  
    //     // printf("I am right!(version1.2) len = %d fs_sel = %d\n",len,fs_sel);  
    //     return len;  
    // }  
    // else  
    // {  
    //     // printf("Sorry,I am wrong!");  
    //     return FALSE;  
    // }       
}  
/********************************************************************  
* 名称:           serial_send  
* 功能:           发送数据  
* 入口参数:        fd                  :文件描述符      
*                 send_buf    :存放串口发送数据  
*                 data_len    :一帧数据的个数  
* 出口参数:        正确返回为0,错误返回为-1  
*******************************************************************/    
int serial_send(int fd, char *send_buf,int data_len)    
{    
    int len = 0;    
    len = write(fd,send_buf,data_len);    
    if (len == data_len )    
    {    
        PRINT("send data is %s\n",send_buf);  
        return TRUE;    
    }         
    else       
    {    
        tcflush(fd,TCOFLUSH);    
        return FALSE;    
    }    
}

测试demo

仅仅是修改中,read是阻塞型的,所以测试的话直接while(1)吧,先写了一路,没开多线程

#include "packet.h"
#include "serial.h"
#include<stdio.h>      /*标准输入输出定义*/  
#include<stdlib.h>     /*标准函数库定义*/  
#include<unistd.h>     /*Unix 标准函数定义*/  
#include<sys/types.h>   
#include<sys/stat.h>     
#include<fcntl.h>      /*文件控制定义*/ 
#define __USE_MISC
#include<termios.h>    /*PPSIX 终端控制定义*/  
#include<errno.h>      /*错误号定义*/  
#include<string.h>  
#include <sys/mman.h>
#include <poll.h>
#define FALSE  -1  
#define TRUE   0  
#define UART_NUM 12
void Uart_Out32(unsigned int * Addr, unsigned int Value)
{
  volatile unsigned int *LocalAddr = (volatile unsigned int *)Addr;
  *LocalAddr = Value;
}
 unsigned int * Uart_In32(unsigned int * Addr)
{
  return *(volatile unsigned int *) Addr;
}
uint8_t *uartdev[UART_NUM] = {
  "/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2", "/dev/ttyS3", 
  "/dev/ttyS4", "/dev/ttyS5", "/dev/ttyS6", "/dev/ttyS7", 
  "/dev/ttyS8", "/dev/ttyS9", "/dev/ttyS10",  "/dev/ttyS11",  
};
int main(int argc, char **argv)  
{  
    int fd;                            //文件描述符  
    int err;                           //返回调用函数的状态  
    int len;                          
    int i;  
    char rcv_buf[0x100];         
    char send_buf[20]="HelloWorld";
      fd = serial_open(uartdev[0]); //打开串口,返回文件描述符  
      err = serial_init(fd,614400,0,8,1,'N');  
        printf("Set Port Exactly!\n");  
        for(i = 0;i < 3;i++)  
        {  
             len = serial_send(fd,send_buf,10);  
            if(len > 0)  
                printf(" %d time send %d data successful\n",i,len);  
            else  
                printf("send data failed!\n");  
            sleep(2);  
        }  
        // serial_close(fd);               
        while (1) //循环读取数据  
        {    
    memset(rcv_buf,0,256);
            len = serial_recv(fd, rcv_buf,100);  
              if(len > 0)  
            {  
                // rcv_buf[len] = '\0';  
    for(i = 0;i < len;i++)
                printf("receive data is 0x%x\n",rcv_buf[i]);  
                printf("len = %d\n",len);  
            }  
            else  
            {  
                printf("cannot receive data\n");  
            }  
            // sleep(2);  
        }              
        // serial_close(fd);   
}

简单好用,对于波特率的修改和进阶在补充篇讲吧


相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
9天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
30 9
|
7天前
|
人工智能 Android开发 数据安全/隐私保护
移动应用与系统:探索开发趋势与操作系统的协同进化####
当今时代,移动应用不再仅仅是简单的软件工具,它们已成为扩展智能手机及平板等设备功能的关键。本文旨在深入分析当前移动应用的开发趋势,探讨移动操作系统的最新进展及其对应用开发的影响,并阐述两者如何相互促进、协同进化,共同推动移动互联网技术向前发展。 ####
|
8天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
25 2
|
12天前
|
人工智能 物联网 Android开发
移动应用与系统:探索开发趋势与操作系统的协同进化####
本文深入探讨了移动应用开发的当前趋势,以及这些趋势如何与移动操作系统的发展相互影响、协同进化。通过分析最新的技术动态、市场数据及用户行为变化,本文旨在为开发者提供关于未来移动应用开发方向的洞察,并讨论操作系统层面的创新如何促进或制约应用的发展。 ####
|
12天前
|
缓存 网络协议 Linux
Linux操作系统内核
Linux操作系统内核 1、进程管理: 进程调度 进程创建与销毁 进程间通信 2、内存管理: 内存分配与回收 虚拟内存管理 缓存管理 3、驱动管理: 设备驱动程序接口 硬件抽象层 中断处理 4、文件和网络管理: 文件系统管理 网络协议栈 网络安全及防火墙管理
35 4
|
11天前
|
安全 网络协议 Linux
Linux操作系统的内核升级与优化策略####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统内核升级的重要性,并详细阐述了一系列优化策略,旨在帮助系统管理员和高级用户提升系统的稳定性、安全性和性能。通过实际案例分析,我们展示了如何安全有效地进行内核升级,以及如何利用调优技术充分发挥Linux系统的潜力。 ####
31 1
|
15天前
|
安全 物联网 Android开发
移动应用与系统:探索开发趋势与操作系统的演进####
【10月更文挑战第29天】 本文深入探讨了移动应用开发的最新趋势与挑战,并分析了主流移动操作系统(如Android、iOS)的发展动态。通过对比不同系统的技术特点和市场表现,揭示了移动应用生态系统的复杂性及其对开发者的影响。此外,还讨论了跨平台开发工具的兴起如何改变应用开发流程,以及这些变化对未来移动计算领域的潜在影响。 ####
30 4
|
14天前
|
物联网 Linux 云计算
Linux操作系统的演变与未来趋势####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统从诞生至今的发展历程,分析了其在服务器、桌面及嵌入式系统领域的应用现状,并展望了云计算、物联网时代下Linux的未来趋势。通过回顾历史、剖析现状、预测未来,本文旨在为读者提供一个全面而深入的视角,以理解Linux在当今技术生态中的重要地位及其发展潜力。 ####
|
18天前
|
人工智能 安全 Linux
|
13天前
|
开发工具 Android开发 数据安全/隐私保护
探索移动应用的世界:从开发到操作系统的全面解析
【10月更文挑战第33天】在数字化时代,移动应用已成为我们日常生活中不可或缺的一部分。本文将深入探讨移动应用的开发过程,包括编程语言、开发工具和框架的选择,以及如何构建用户友好的界面。同时,我们还将分析移动操作系统的核心功能和安全性,以帮助读者更好地理解这些应用程序是如何在各种设备上运行的。无论你是开发者还是普通用户,这篇文章都将为你揭示移动应用背后的奥秘。

热门文章

最新文章