这个话题,你们可能再熟悉不过了,网上资料不少,由于这是
linux
下编程比较重要的一个方面,懂这方面的人不少;这里我只是想给初学者简单的介绍下这方面的知识:
串口编程其实说白了,
是拿根串口线把电脑和所要控制的机器链接起来,而后在经过编程的方式对下位机进行发送指定的数据或进行控制,或进行传输,而后在接受下位机反馈回来的信息提示是否已经正确。是否是好俗!
串口是计算机上一种很是通用设备通讯的协议,经常使用
PC
机上包含的是
RS232
规格的串口,固然,除了
RS232
,还有
RS485
和
RS422
两种规格,用于不一样的设备通讯;这里主要是介绍
RS232
串口编程。
在串口编程中,比较重要的是串口的设置,咱们要设置的部分包括波特率,数据位,中止位,奇偶校验位;要注意的是,每台机器的串口默认设置多是不一样的,若是你没设置这些,仅仅按照默认设置进行发送数据,极可能出现
n
多异想不到而又查不出来的状况;因此,在真正通信前,咱们必须设置这些:
下面就开始介绍这些基本设置的函数,(其实都是些固定框架,程序中稍微改改就行)
~o~
1.
设置波特率
注意每台机器都有输出和输入接受信息的速度
,因此用
cfsetispeed
和
cfsetospeed
来分别设置;注意到
struct termios
这样一个结构,它包括了串口端全部的设置,下面还要用到。它在
termios.h
中被定义。。还有一个地方比较难以理解,为何设置了
speed_arr
和
name_arr
两个数组,这是由于在
linuxe
下,系统为波特率专门准备了一张表用
B38400,B19200......
代替,而咱们实际上传进去的只能是
38400,19200
这些值,因此咱们拿咱们传进去的和
name_arr
进行比较,若是相等则从系统对照表中取出相应值进行设置,若是不等证实传的值在系统对照表中没有,则不进行设置。
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300, //
B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300,
38400, 19200, 9600, 4800, 2400, 1200, 300, };
void set_speed(int fd, int speed)
{
int i;
int status;
struct termios Opt; //
定义了这样一个结构
tcgetattr(fd, &Opt); //
用来获得机器原端口的默认设置
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
{
if (speed == name_arr) //
判断传进来是否相等
{
tcflush(fd, TCIOFLUSH); //
刷新输入输出缓冲
cfsetispeed(&Opt, speed_arr); //
这里分别设置
cfsetospeed(&Opt, speed_arr);
status = tcsetattr(fd, TCSANOW, &Opt); //
这是马上把
bote rates
设置真正写到串口中去
if (status != 0)
perror("tcsetattr fd1"); //
设置错误
return;
}
tcflush(fd,TCIOFLUSH); //
同上
}
}
2
。设置奇偶校验,数据,中止位
这三个参数一般放在一块儿设置,
databits
是数据位,
stopbits
是中止位,
parity
是校验位。
串口的这些设置是很复杂很复杂的,
Termios
成员中共定义
c_cflag
控制项
c_lflag
线路项
c_iflag
输入项
c_oflag
输出项
c_cc
控制字符
c_ispeed
输入波特
c_ospeed
输出波特
那么多项,对于每一项都有不少的设置,这里咱们不讲的那么复杂,就一个通用的串口框架进行解释,主要进行奇偶校验,数据,中止位的设置。而其余的一些控制项,在程序中用到时穿插讲解:
int set_Parity(int fd,int databits,int stopbits,int parity)
{
struct termios options; //
定义一个结构
if ( tcgetattr( fd,&options) != 0) //
首先读取系统默认设置
options
中
,
必须
{
perror("SetupSerial 1");
return(FALSE);
}
options.c_cflag &= ~CSIZE; //
这是设置
c_cflag
选项不按位数据位掩码
switch (databits) /*
设置数据位数
*/
{
case 7:
options.c_cflag |= CS7; //
设置
c_cflag
选项数据位为
7
位
break;
case 8:
options.c_cflag |= CS8; //
设置
c_cflag
选项数据位为
8
位
break;
default:
fprintf(stderr,"Unsupported data size\n"); //
其余的都不支持
return (FALSE);
}
switch (parity) //
设置奇偶校验,
c_cflag
和
c_iflag
有效
{
case 'n':
case 'N': //
无校验
固然都不选
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o': //
奇校验
其中
PARENB
校验位有效;
PARODD
奇校验
INPCK
检查校验
case 'O': options.c_cflag |= (PARODD | PARENB); /*
设置为奇效验
*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E': //
偶校验,奇校验不选就是偶校验了
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /*
转换为偶效验
*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
default:
fprintf(stderr,"Unsupported parity\n");
return (FALSE);
}
/*
设置中止位
*/
switch (stopbits) //
这是设置中止位数,影响的标志是
c_cflag
{
case 1:
options.c_cflag &= ~CSTOPB; //
不指明表示一位中止位
break;
case 2:
options.c_cflag |= CSTOPB; //
指明
CSTOPB
表示两位,只有两种可能
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n') //
这是设置输入是否进行校验
options.c_iflag |= INPCK;
//
这个地方是用来设置控制字符和超时参数的,通常默认便可。稍微要注意的是
c_cc
数组的
VSTART
和
VSTOP
元素被设定成
DC1
和
DC3
,表明
ASCII
标准的
XON
和
XOFF
字符。因此若是在传输这两个字符的时候就传不过去,这时须要把软件流控制屏蔽
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_cc[VTIME] = 150; // 15 seconds
options.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH); /* Update the options and do it NOW */ //
刷新和马上写进去
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
return (TRUE);
}
//
串口设置框架到这里就大概结束了,对于设置了数据位校验位中止位和波特率的端口已经能够传输大多数信息。在实际中的状况每每是不少特例,好比,
在用
write
发送数据时没有键入回车,信息就将发送不出去的状况,这主要是由于咱们在输出输入时是按照
规范模式接受到回车或者换行才发送,而不少状况咱们是不须要回车和换行的,这时,应当切换到行方式输入,设置
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
不经处理直接发送。
又好比
在咱们发送字符
0x0d
的时候,每每接受端获得的字符是
0x0a
这是怎么回事,缘由是在串口设置中
c_iflag
和
c_oflag
中存在从
NL-CR
和
CR-NL
的映射,也就是说,串口能够把回车和换行当作一个字符,因此,此时咱们应该屏蔽掉这些,用
options.c_oflag &=~(INLCR|IGNCR|ICRNL|);
和
options.c_oflag &=~(ONLCR|OCRNL);
进行设置。
总之,串口的设置是很复杂也很麻烦的东西,具体状况要具体分析,找到相应的办法,若是发现数据不能传送,不妨耐点心在串口设置上找答案吧
言归正传,后面的东西就很简单了,接下来是打开串口:
int OpenDev(char *Dev)
{
int fd = open( Dev, O_RDWR ); //| O_NOCTTY | O_NDELAY
这种方式看
open
函数
if (-1 == fd)
{ /*
设置数据位数
*/
perror("Can't Open Serial Port");
return -1;
}
else
return fd;
}
而后是数据的接受和发送,把通用的主函数贴下来,很容易的。
int main(int argc, char **argv)
{
int fd;
int nread;
char buff[512];
char *dev ="/dev/ttyS0"; //linux
下的端口就是经过打开设备文件操做的
fd = OpenDev(dev); //
打开
if (fd>0)
set_speed(fd,19200); //
打开后设置波特率
19200
else
{
printf("Can't Open Serial Port!\n");
exit(0);
}
if (set_Parity(fd,8,1,'N')== FALSE) //
设置
8
,
1
,
n
注意,这里和上面要和下位机相符才可能通讯
{
printf("Set Parity Error\n");
exit(1);
}
//
通常读的时候通常都用
read
,写的时候通常都用
write,read
要注意阻塞后程序中止不动,因此要用
select
进行控制,注意
tv
每次循环都要设置;
write
不用考虑阻塞,但要用循环写方式保证必定写完,其实读最好也用循环读方式保证必定能读到全部东西而且能拼接在一块儿,而后在进行其余操做。最后
while (1)
是串口通信中经常使用的循环
就是一直执行,直到碰到
break;
这些东西挺烦琐,不过其实也没什么。这里就不详细说了,下面是个最最简单的。。
while(1)
{
while((nread = read(fd,buff,512))>0)
{
printf("\nLen %d\n",nread);
buff[nread+1]='\0';
printf("\n%s",buff);
}
}
//close(fd);
//exit(0);
}
完了,是否是不难,其实除了串口设置是新知识,,事实上
linux
都是文件,串口是设备文件,设置好后,其余的东西就当成文件进行操做吧。