串口------是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。将CPU的并行数据(一般是8位)转换成比特
串口通讯------指的是计算机依次以位(bit)为单位来传送数据,串行通讯使用的范围很广,在嵌入式系统开发过程中串口通讯也经常用到通讯方式之一。
在Linux系统中,“一切皆文件”。所以在Linux下串口就是一个文件,使用它就和使用文件一样。为了访问串口,只需打开其设备文件即可操作串口设备。在linux系统下面,每一个串口设备都有设备文件与其关联,设备文件位于系统的/dev目录下面。如linux下的/ttyS0,/ttyS1分别表示的是串口1和串口2。下面来详细介绍linux下是如何使用串口的:
大概流程为:打开串口(文件)–>设置串口参数–>读串口–>关闭串口
就和文件操作一模一样
这里的串口将接受和发送的接口接在一起,重硬件上保证了连通在一起
在本程序中,使用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)
串口通信基本参数参考链接
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 刷清输入、输出队列;
*/
波特率定义在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);/* 设置输出波特率 */
要实现串口通信,一定要将输入输出的波特率设置成一样
停止位就是用几个位表示通讯结束,CSTOPB 设置为两个停止位。(默认为1个)
串口通信是按bit传输,数据位指的是每字节中实际数据所占的比特数。要修改数据位可以通过修改termios结构体中c_cflag成员来实现。CS5、CS6、CS7和CS8分别表示数据位为5、6、7和8。值得注意的是,在设置数据位时,必须先使用CSIZE做位屏蔽。
奇偶校验可以选择偶校验、奇校验、空格等方式,也可以不使用校验。如果要设置为偶校验的话,首先要将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;
直接调用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参数打开串行口后,同样与使用了该函数调用。
引用链接
直接调用close系统调用关闭打开的文件描述符
close(fd);
/*********************************************************************************
* 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;
}