简介

串口也称串行通信接口(通常指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);
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐