简介
串口也称串行通信接口(通常指COM接口),是实际工作中经常使用的一个接口,比如Linux下使用的debug串口,它用来登录Linux系统,输出log。另外也会使用串口和外部的一些模块通信,比如GPS模块、RS485等。串口通信的两种最基本的方式:同步串行通信方式和异步串行通信方式。异步串行是指UART(UniversalAsynchronous Receiver/Transmitter)通用异步收发传输器,通信双方接三根线,RX、TX和GND,TX用于发送数据,RX用于接受数据,双方收发交叉对接,支持全双工方式,包含TTL电平的串口和RS232电平的串口。TTL电平是3.3V的,而RS232是负逻辑电平。一般软件配置串口,有波特率,数据位、停止位、校验位、流控。分别表示传输速度,一帧数据的长度,以及发完告知停止,发完是否校验,是否进行发送控制。默认是固定8位数据位,1位停止位、无校验、无流控,只需配置波特率。
RS-232
最常用的一种串行通讯接口。它的全名是“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”。传统的RS-232-C接口标准有22根线,采用标准25芯D型插头座(DB25),后来使用简化为9芯D型插座(DB9),现在应用中25芯插头座已很少采用。RS-232采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为2V至3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约15米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kΩ。所以RS-232适合本地设备之间的通信。
RS-485
RS-485可以采用二线与四线方式,二线制可实现真正的多点双向通信,而采用四线连接时只能实现点对多的通信,即只能有一个主(Master)设备,其余为从设备,无论四线还是二线连接方式总线上可多接到32个设备。RS-485与RS-422的不同还在于其共模输出电压是不同的,RS-485是-7V至+12V之间,而RS-422在-7V至+7V之间,RS-485接收器最小输入阻抗为12kΩ、RS-422是4kΩ;由于RS-485满足所有RS-422的规范,所以RS-485的驱动器可以在RS-422网络中应用。RS-485与RS-422一样,其最大传输距离约为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能使用规定最长的电缆长度。只有在很短的距离下才能获得最高速率传输。一般100米长双绞线最大传输速率仅为1Mb/s。
linux下的串口操作
Linux的串口表现为设备文件。串口设备文件命名一般为/dev/ttySn,若串口是USB扩展的,则串口设备文件命名多为/dev/ttyUSBn。不同的硬件平台对串口设备文件的命名有所区别。
linux终端操作串口
可使用microcom工具操作串口,如下:microcom -s 115200 /dev/ttyS1 /dev下的ttyS1对应的就是UART1设备。 microcom 命令后的-s 115200,表示设置波特率为115200bps。 micrcom指令退出的方式是Ctrl+x,不是Ctrl+c。
打开串口
在编写Linux串口程序时,需要包含termios.h头文件:
#include <termios.h>
在使用某个串口前,必须用open()函数打开它所对应的设备文件。如下:
int fd;
fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY );
if (fd < 0) {
perror("open uart device error\n");
}
当open调用成功后,将返回文件描述符,并作为其它操作函数的参数;如果失败返回负数。在打开串口时,除了需要用到O_RDWR选项标志外,通常还需要使用O_NOCTTY,目的是告诉Linux“本程序不作为串口的‘控制终端’”。如果不使用该选项,会有一些输入字符影响进程运行(如一些产生中断信号的键盘输入字符等)。
关闭串口
当不再使用某个串口时,可用close()函数关闭串口:
close(fd); 参数fd为打开串口时得到的文件描述符。
发送数据
往串口发送数据可通过write()函数完成。
int len;
char buf[] = "hello world!";
len = write(fd, buf, sizeof(buf));
if (len< 0) {
printf("write data to serial failed! \n");
}
字符串的长度为sizeof(buf),作为write()函数的发送数据长度参数。写操作完成后,返回值为成功发送数据的长度;如果发送失败,返回负数。
设置串口参数
串口通讯波特率设置
波特率的设置定义在<termios.h>里。设置波特率使用cfsetispeed( )和cfsetospeed( )函数来操作,获取波特率信息是通过cfgetispeed()和cfgetospeed()函数来完成的。
struct termios opt;
cfsetispeed(&opt,B9600 );
cfsetospeed(&opt,B9600);
一般来说,输入、输出的波特率应该是一致的。
串口属性配置
属性定义在结构体struct termios中。头文件<termbits.h>
该结构体定义如下:
// 配置串口属性结构
struct termios{
tcflag_t c_iflag; /* 设置c_iflag可以设置输入模式标志 */
tcflag_t c_oflag; /* 设置c_oflag可以设置输出模式标志 */
tcflag_t c_cflag; /* 设置c_cflag可以设置控制模式标志 */
tcflag_t c_lflag; /* c_lflag为本地模式标志* /
cc_t c_line; / *c_line 为线规划* /
cc_t c_cc[NCCS]; / *cc_cc[NCCS]为控制字符* /
speed_t c_ispeed; / *c_ispeed为输入速率*/
speed_t c_ospeed; / *c_ospeed为输出速率 */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};
// c_iflag取值
#define IGNBRK 0000001//ignore break 忽略break键输入
#define BRKINT 0000002//当break情况被检测到发送SIGINT信号#define IGNPAR 0000004 //忽略帧错误和奇偶校验错误
#define PARMRK 0000010//标识奇偶校验错误
#define INPCK 0000020//启用输入奇偶校验
#define ISTRIP 0000040//去除字符的第8个比特
#define INLCR 0000100//将输入的NL(换行)转换成CR(回车)
#define IGNCR 0000200//忽略输入中的回车
#define ICRNL 0000400//将CR(回车)转换成NL(换行)
#define IUCLC 0001000//将输入的大写字符转换成小写字符(非POSIX)
#define IXON 0002000//开启输入时XON/XOFF流控制
#define IXANY 0004000//输入任何字符将重启停止时的输出
#define IXOFF 0010000//关闭输入时XON/XOFF流控制
#define IMAXBEL 0020000//当输入队列满时开始响铃
#define IUTF8 0040000//使用UTF-8编码输入
// bits/termio.h中其他定义
/* tcflow() and TCXONC use these /
#define TCOOFF 0// 挂起输出
#define TCOON 1//重新开始被挂起的输出
#define TCIOFF 2//发送一个 STOP 字符,停止终端设备向系统传送数据#
define TCION 3//发送一个 START 字符,使终端设备向系统传输数据/打开一个终端设备时的默认设置是输入和输出都没有挂起。*/
/* tcflush() and TCFLSH use these 清除缓冲区数据*/
#define TCIFLUSH 0//清除输入缓冲区
#define TCOFLUSH 1//清除输出缓冲区
#define TCIOFLUSH 2//清除输入输出缓冲区
/* tcsetattr uses these */
#define TCSANOW 0//不等数据传输完毕就立即改变属性
#define TCSADRAIN 1//等待所有数据传输结束才改变属性#define TCSAFLUSH 2//清空输入输出缓冲区才改变属性
#define IOT_termios /* Hurd ioctl type field. */ \ _IOT (IOTS (cflag_t), 4, _IOTS (cc_t), NCCS, _IOTS (speed_t), 2)
VTIME 和 VMIN
VTIME 定义要求等待的零到几百毫秒的值(通常是一个8位的unsigned char变量)。
VMIN 定义了要求等待的最小字节数, 这个字节数可能是0。只有设置为阻塞时这两个参数才有效,仅针对于读操作。
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 0;
VMIN = 0,当缓冲区字节数 >= 0 时进行读操作,实际上这时读串口操作并未被阻塞,因为条件始终被满足。
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;VMIN = 1,当缓冲区字节数 >= 1 时进行读操作,当没有数据时读串口操作被阻塞。options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 4;
VMIN = 4,当缓冲区字节数 >= 4 时进行读操作,否则读串口操作被阻塞。每次读出的最大字节数由read函数中第三个参数决定。直到缓冲区剩下的数据< read 第三个参数 并且< 4 (如果这时read第三参数为 1 则进行4次读操作直至读完缓冲区,如read第三参数为2,连续进行读操作,直至缓冲区空或还剩一个字符)。没有设置VTIME,剩下的字符没有确定的期限,直到下次满足读条件的时候才被读出。
options.c_cc[VTIME] = 10; //单位百毫秒
options.c_cc[VMIN] = 4;同3的区别就是,没满足条件或读缓冲区中剩下的数据会在1秒(10百毫秒)后读出。另外特别注意的是当设置VTIME后,如果read第三个参数小于VMIN ,将会将VMIN 修改为read的第三个参数,即,使用read(fd,&buf,2);,以上设置变为:
options.c_cc[VTIME] = 10;
options.c_cc[VMIN] = 2;
读取数据
使用read()函数可以读取串口接收到的数据。
int len;unsigned char buf[11];
len = read(fd, buf, 11);
if (len < 0){
printf("reading data faile \n");
}
读取成功,函数返回所读数据长度;失败返回负数。
使用范例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#define DEV_NAME "/dev/ttyS1"
int main (int argc, char *argv[])
{
int fd;
int len, i,ret;
char buf[] = "hello world!";
fd = open(DEV_NAME, O_RDWR | O_NOCTTY);if(fd < 0) { perror(DEV_NAME); return -1;}
struct termios uart_cfg_opt;speed_t speed = B9600;
if (-1 == tcgetattr(fd, &uart_cfg_opt))
return -1;
tcflush(fd, TCIOFLUSH);
cfsetospeed(&uart_cfg_opt, speed);cfsetispeed(&uart_cfg_opt, speed);
if (-1 == tcsetattr(fd , TCSANOW, &uart_cfg_opt))
return -1;
uart_cfg_opt.c_cc[VTIME] = 1;
uart_cfg_opt.c_cc[VMIN] = 0;
/* Data length setting section */uart_cfg_opt.c_cflag &= ~CSIZE;uart_cfg_opt.c_cflag |= CS8;
uart_cfg_opt.c_iflag &= ~INPCK;
uart_cfg_opt.c_cflag &= ~PARODD;
uart_cfg_opt.c_cflag &= ~CSTOPB;
/* Using raw data mode */
uart_cfg_opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);uart_cfg_opt.c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON | IXOFF);uart_cfg_opt.c_oflag &= ~(INLCR | IGNCR | ICRNL);uart_cfg_opt.c_oflag &= ~(ONLCR | OCRNL);
/* Apply new settings */
if (-1 == tcsetattr(fd , TCSANOW, &uart_cfg_opt))
return -1;
tcflush(fd , TCIOFLUSH);
len = write(fd, buf, sizeof(buf)); /* 向串口写入字符串 */
if (len < 0) { printf("write data error \n");}
len = read(fd, buf, sizeof(buf)); /* 在串口读入字符串 */
if (len < 0) { printf("read error \n"); return -1;}
printf("%s", buf); /* 打印从串口读出的字符串 */
return(0);
}