Linux终端io------串口通信C语言实现自发自收

1.串口通信

串口------是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。将CPU的并行数据(一般是8位)转换成比特
串口通讯------指的是计算机依次以位(bit)为单位来传送数据,串行通讯使用的范围很广,在嵌入式系统开发过程中串口通讯也经常用到通讯方式之一。

在Linux系统中,“一切皆文件”。所以在Linux下串口就是一个文件,使用它就和使用文件一样。为了访问串口,只需打开其设备文件即可操作串口设备。在linux系统下面,每一个串口设备都有设备文件与其关联,设备文件位于系统的/dev目录下面。如linux下的/ttyS0,/ttyS1分别表示的是串口1和串口2。下面来详细介绍linux下是如何使用串口的:

大概流程为:打开串口(文件)–>设置串口参数–>读串口–>关闭串口
就和文件操作一模一样
这里的串口将接受和发送的接口接在一起,重硬件上保证了连通在一起

2.打开串口

在本程序中,使用ttyUSB0作为通信串口。在打开ttyUSB0的时候,基本选项O_RDWR表示可读写,选项 O_NOCTTY 表示不能把本串口当成控制终端,否则用户的键盘输入信息将影响程序的执行; O_NDELAY表示打开串口的时候,程序并不关心另一端 的串口是否在使用中。 O_NDELAY 函数使read函数在端口没有字符存在的情况下,立刻返回0,与 fcntl(fd,F_SETFL,FNDELAY);一样,也与O_NONBLOCK(程序不关心DCD信号线的状态,如果指定该标志,进程将一直在休眠状态,直到DCD信号线为0)一样。得到串口的文件描述符后面就可以通过fd来操作它。B要想解析出A发的比特流是什么意思,就需要有一个参考(时间同步)这就是波特率。同时为了检查出数据的正确与否,又加入了奇偶校验。

char *serialport_path = "/dev/ttyUSB0";
int serial_fd = -1;
serial_fd = open(serialport_path,O_RDWR | O_NOCTTY | O_NDELAY)

3.设置串口

串口通信基本参数参考链接
termios结构体每个标志表示的意思参考链接
实现串口通信并不是说通信就通信的,有很多问题需要解决。假如我发送一个数据,A发到B,那么B怎么知道数据到没到和数据有没有结束要不要继续等待,这样它的数据报就要有标志起始和停止的。串口的属性定义在struct termios中,结构体定义在termios.h头文件中。该结构体通过控制驱动程序控制字符的输入输出到用户进程。
结构体中flag的赋值通过位运算,置某位用 | ,设某位为0用 & 和 ~。
在设置终端属性之前需要用冲洗函数,冲洗掉相应的队列

	 /*struct termios
     * {unsigned short c_iflag;  输入模式标志
     * unsigned short c_oflag;  输出模式标志
     * unsigned short c_cflag;  控制模式标志
     * unsigned short c_lflag; 区域模式标志或本地模式标志或局部模式
     * unsigned char c_line; 行控制line discipline 
     * unsigned char c_cc[NCC];  控制字符特性,所有可以更改的特殊字符
     * };
     *  */
     //控制函数
     int tcgetattr(int filedes, struct termios *termptr);/* 获取终端属性 */
 
	 int tcsetattr(int filedes, int opt, const struct termios *termptr);/* 设置终端属性 */
/*
 * 说明:
 * filed是终端设备描述符;
 *
 * opt参数可以指定为以下的值:
 * TCSANOW          更改立即生效;
 * TCSADRAIN        发送所有输出后更改才发生,若更改输出参数则应该使用此选项;
 * TCSAFLUSH        发送所有输出后更改才发生,更进一步,在更改发生时未读的所有输入数据都被删除;
 */

//终端行控制函数
int tcdrain(int filedes);
int tcflow(int filedes, int action);
int tcflush(int filedes, int queue);
int tcsendbreak(int filedes, int duration);
/*
 * 说明:
 * action参数取值如下:
 * TCOOFF       输出被挂起;
 * TCOON        重新启动以前被挂起的输出;
 * TCIOFF       系统发送一个STOP字符,将使终端设备暂停发送数据;
 * TCION        系统发送一个START字符,将使终端设备恢复发送数据;
 *
 * queue参数取值如下:
 * TCIFUSH      刷清输入队列;
 * TCOFUSH      刷清输出队列;
 * TCIOFUSH      刷清输入、输出队列;
 */

Linux终端io------串口通信C语言实现自发自收_第1张图片
在这里插入图片描述

3.1波特率

波特率定义在struct termios中结构体中,不同的系统对它的表示不一样,所以有了两个控制波特率的函数

/* 返回值:若成功则返回波特率值 */
speed_t cfgetispeed(const struct termios *termptr);/* 获取输入波特率 */
 
speed_t cfgetospeed(const struct termios *termptr);/* 获取输出波特率 */
/* 返回值:若成功则返回0,出错则返回-1;*/
int cfsetispeed(struct termios *termptr, speed_t speed);/* 设置输入波特率 */
 
int cfsetospeed(struct termios *termptr, speed_t speed);/* 设置输出波特率 */

要实现串口通信,一定要将输入输出的波特率设置成一样

3.2停止位

停止位就是用几个位表示通讯结束,CSTOPB 设置为两个停止位。(默认为1个)

3.3数据位

串口通信是按bit传输,数据位指的是每字节中实际数据所占的比特数。要修改数据位可以通过修改termios结构体中c_cflag成员来实现。CS5、CS6、CS7和CS8分别表示数据位为5、6、7和8。值得注意的是,在设置数据位时,必须先使用CSIZE做位屏蔽。

3.4奇偶检验位

奇偶校验可以选择偶校验、奇校验、空格等方式,也可以不使用校验。如果要设置为偶校验的话,首先要将termios结构体中c_cflag设置 PARENB标志,并清除PARODD标志。如果要设置奇校验,要同时设置termios结构体中c_cflag设置PARENB标志和PARODD标 志。如果不想使用任何校验的话,清除termios结构体中c_cflag的PARENB位。

//无校验
opt.c_cflag &= ~PARENB;

//奇校验

opt.c_cflag |= (PARODD | PARENB);

//偶校验

opt.c_cflag |= PARENB;

opt.c_cflag &= ~PARODD;

4.读串口

直接调用read函数。这里终端 IO 有两种不同的工作模式:
规范模式输入处理:在这种模式下,终端输入以行为单位进行处理,对于每个读要求,终端驱动程序最多返回一行;
非规范模式输入处理:输入字符不以行为单位进行处理;
在非标准的输入程序模式下, 输入的资料不会被组合成一行而输入后的处理功能 (清除, 杀掉, 删除, 等等.) 都不能使用. 这个模式有两个功能控制参数: c_cc[VTIME] 设定字元输入时间计时器, 及 c_cc[VMIN] 设定满足读取功能的最低字元接收个数.
c_cc[VMIN]: 最少可读数据
c_cc[VTIME]: 等待数据时间(10秒的倍数)

从端口读数据则需要些技巧。如果在原始数据的模式下对端口进行操作,read()系统调用将返回串行口输入缓冲区中所有的字符数据,不管有多少, 如果没有数据,那么该调用将被阻塞.处于等待状态,直到有字符输入,或者到了规定的时限和出现错误为止,
通过以下方法,能使read函数立即返回。

fcntl(fd,F_SETFL,FNDELAY);

FNDELAY 函数使read函数在端口没有字符存在的情况下,立刻返回0,
如果要恢复正常(阻塞)状态,可以调用fcntl()函数,不要FNDELAY参数,如下所示:

fcntl(Fd,F_SETFL,0);

在使用O_NDELAY参数打开串行口后,同样与使用了该函数调用。
在这里插入图片描述

引用链接

5.关闭串口

直接调用close系统调用关闭打开的文件描述符
close(fd);

6源码

/*********************************************************************************
 *      Copyright:  (C) 2019 wujinlong<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  serial_com.c
 *    Description:  This file Achieve serial communication
 *                 
 *        Version:  1.0.0(17/05/19)
 *         Author:  wujinlong<[email protected]>
 *      ChangeLog:  1, Release initial version on "17/05/19 15:57:27"
 *                 
 ********************************************************************************/

#include 
#include 
#include 
#include 
#include 
#include 
#include
#include 
#include 

int main(int argc,char *argv[])
{
    struct termios old_term,new_term;//该结构体控制驱动程序控制字符的输入
    /*struct termios
     * {unsigned short c_iflag;  输入模式标志
     * unsigned short c_oflag;  输出模式标志
     * unsigned short c_cflag;  控制模式标志
     * unsigned short c_lflag; 区域模式标志或本地模式标志或局部模式
     * unsigned char c_line; 行控制line discipline 
     * unsigned char c_cc[NCC];  控制字符特性,所有可以更改的特殊字符
     * }; */
    char *serialport_path = "/dev/ttyUSB0";
    //打开串口
    int serial_fd = -1;
    /* O_NDELAY 函数使read函数在端口没有字符存在的情况下,立刻返回0,与 fcntl(fd,F_SETFL,FNDELAY);一样 */
   if((serial_fd = open(serialport_path,O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
   {
       printf("open a serialport failure:%s\n",strerror(errno));
       return -1;
   }
    if(isatty(serial_fd) == 0)
    {
        printf("open fd is not a terminal device\n");
        return -1;
    }
        if(tcgetattr(serial_fd,&old_term) < 0)
    {
        printf("tcgetattr failure:%s\n",strerror(errno));
        return -1;
    }
    bzero((void *)&new_term,sizeof(new_term));
        new_term.c_cflag |= CLOCAL | CREAD;//用于本地连接和接收使用
    //new_term.c_lflag |= ICANON;//规范输入
    //数据位
    new_term.c_cflag &= ~CSIZE;//设置数据位前必须先使用CSIZE做位屏蔽
    new_term.c_cflag |= CS8;
    new_term.c_cflag &= ~CRTSCTS;//无硬件流控 
    //奇偶校验
    new_term.c_cflag |= PARENB;//启用奇偶检验
    new_term.c_iflag |= INPCK;//打开输入奇偶校验
    new_term.c_cflag &= ~PARODD;//偶检验
    //new_term.c_iflag |= IGNPAR;//无奇偶检验位 
    new_term.c_oflag = 0; //输出模式 
    new_term.c_lflag = 0; //不激活终端模式 
    //停止位
    new_term.c_cflag &=  ~CSTOPB;
    //波特率
    new_term.c_oflag &= ~OPOST;/*如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯。*/
    if(cfsetispeed(&new_term,B115200) < 0)
    {
        printf("cfsetispeed failure:%s\n",strerror(errno));
        return -3;
    }
    if(cfsetospeed(&new_term,B115200) < 0)
    {
        printf("cfsetospeed failure:%s\n",strerror(errno));
        return -3;
    }
    //只有在输出队列为空时才能改变一个终端的属性,所以要用tcflush;
    tcflush(serial_fd,TCIFLUSH);
    if(tcsetattr(serial_fd,TCSANOW,&new_term) != 0)
    {
        printf("tcsetattr failure:%s\n",strerror(errno));
        return -4;
    }
    //tcsetattr函数执行了任意一种所要求的动作,也会返回OK,需调用tcgetaddr检查;
    char buff[] = "hello\n";
    char buf[10];
    memset(&buf,0,sizeof(buf));
    fcntl(serial_fd,F_SETFL,FNDELAY);
    int rv = -1;
   if((rv = write(serial_fd,buff,sizeof(buff))) < 0)
   {
       printf("write failure:%s\n",strerror(errno));
       return -5;
          }
   printf("write %dbytes to serial ok!wait 1s...\n",rv);
   sleep(1);
   if((rv = read(serial_fd,buf,sizeof(buf))) < 0)
   {
       printf("read failure:%s\n",strerror(errno));
       return -5;
   }
   printf("read data from serialport:%s",buf);

   tcflush(serial_fd,TCIOFLUSH);
   tcsetattr(serial_fd,TCSANOW,&old_term);

   close(serial_fd);
   return 0;
}
       

你可能感兴趣的:(Linux学习笔记)