linux与开发板串口通信
研究了一天的linux串口,结果改了树莓派的系统配置文件config.txt给改了导致系统崩溃。。。。其实我感觉网上的大多数方法都是不符合新版本树莓派的,网上的方法是通过修改系统配置文件后安装minicom进行串口的调试。为什么需要修改配置文件?因为树莓派升级后tx与rx引脚是复用的,需要用于串口的话就需要修改配置,让系统把io口让给串口。
这种方法比较麻烦,我采用的是利用两个usb转串口,互相连接好了,就可以直接通过linux下的串口通信函数来实现通信了。需要注意的是两个usb转串口相互连接时不仅仅要将RXD、TXD相互反接,还需要将GND连接在一起。
下面讲解下具体方法
(1)不同系统的串口名称是不一样的,如下图。
(2)设置
最基本的设置串口包括波特率设置,效验位和停止位设置.
很多系统都支持POSIX终端(串口)接口.程序可以利用这个接口来改变终端的参数,比如,波特率,字符大小等等.要使用这个端口的话,你必须将<termios.h>头文件包含到你的程序中.这个头文件中定义了终端控制结构体和POSIX控制函数.
与串口操作相关的最重要的两个POSIX函数可能就是tcgetattr(3)和tcsetattr(3).顾名思义,这两个函数分别用来取得设设置终端的属性.调用这两个函数的时候,你需要提供一个包含着所有串口选项的termios结构体,串口的设置主要是设置struct termios结构体的各成员值.
通过termio结构体的c_cflag成员可以控制波特率,数据的比特数,parity,停止位和硬件流控制,下面这张表列出了所有可以使用的常数
进行设置时千万不要直接用使用数字来初始化c_cflag(当然还有其他标志),最好的方法是使用位运算的与或非组合来设置或者清除这个标志.不同的操作系统版本会使用不同的位模式,使用常数定义和位运算组合来避免重复工作从而提高程序的可移植性.
1.1波特率设置
不同的操作系统会将波特率存储在不同的位置.旧的编程接口将波特率存储在上表所示的c_cflag成员中,而新的接口实装则提供了c_ispeed和c_ospeed成员来保存实际波特率的值.
程序中可是使用cfsetospeed(3)和cfsetispeed(3)函数在termios结构体中设置波特率而不用去管底层操作系统接口.下面的代码是个非常典型的设置波特率的例子.
struct termios options; tcgetattr(fd,&option); //获取端口的当前状态 cfsetispeed(&options,B19200);//设置波特率为19200 options.c_cflags | =(CLOCK | CREAD);//将设置的参数传入 tcsetattr(fd,TCSANOW.&options);
函数tcgetattr( )会将当前串口配置回填到termio结构体option中.然后,程序设置了输入输出的波特率并且将本地模式(CLOCAL)和串行数据接收(CREAD)设置为有效,接着将新的配置作为参数传递给函数tcsetattr( ).常量TCSANOW标志所有改变必须立刻生效而不用等到数据传输结束.其他另一些常数可以保证等待数据结束或者刷新输入输出之后再生效.
1.2 设置字符大小,设置字符大小的时候,没有像设置波特率那么方便的函数.所以,程序中需要一些位掩码运算来把事情搞定.字符大小以比特为单位指定:
1 options.c_flag &= ~CSIZE; /* Mask the character size bits */ 2 options.c_flag |= CS8; /* Select 8 data bits */
1.3 与上同理,通过位掩码来设置校验位与停止位
无奇偶校验( no parity)
1 options.c_cflag &= ~PARENB 2 options.c_cflag &= ~CSTOPB 3 options.c_cflag &= ~CSIZE; 4 options.c_cflag |= CS8;
偶校验(even parity)
1 options.c_cflag |= PARENB 2 options.c_cflag &= ~PARODD 3 options.c_cflag &= ~CSTOPB 4 options.c_cflag &= ~CSIZE;
5 options.c_cflag |= CS7;
奇校验(odd parity)
1 options.c_cflag |= PARENB 2 options.c_cflag |= PARODD 3 options.c_cflag &= ~CSTOPB 4 options.c_cflag &= ~CSIZE; 5 options.c_cflag |= CS7;
设置奇偶校验函数
/** *@brief 设置串口数据位,停止位和效验位 *@param fd 类型 int 打开的串口文件句柄 *@param databits 类型 int 数据位 取值 为 7 或者8 *@param stopbits 类型 int 停止位 取值为 1 或者2 *@param parity 类型 int 效验类型 取值为N,E,O,,S */ int set_Parity(int fd,int databits,int stopbits,int parity) { struct termios options; if ( tcgetattr( fd,&options) != 0) { perror("SetupSerial 1"); return(FALSE); } options.c_cflag &= ~CSIZE; switch (databits) /*设置数据位数*/ { case 7: options.c_cflag |= CS7; break; case 8: options.c_cflag |= CS8; break; default: fprintf(stderr,"Unsupported data size\n"); return (FALSE); } switch (parity) { case 'n': case 'N': options.c_cflag &= ~PARENB; /* Clear parity enable */ options.c_iflag &= ~INPCK; /* Enable parity checking */ break; case 'o': 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; case 'S': case 's': /*as no parity*/ options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB;break; default: fprintf(stderr,"Unsupported parity\n"); return (FALSE); } /* 设置停止位*/ switch (stopbits) { case 1: options.c_cflag &= ~CSTOPB; break; case 2: options.c_cflag |= CSTOPB; break; default: fprintf(stderr,"Unsupported stop bits\n"); return (FALSE); } /* Set input parity option */ if (parity != 'n') options.c_iflag |= INPCK; tcflush(fd,TCIFLUSH); options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/ options.c_cc[VMIN] = 0; /* Update the options and do it NOW */ if (tcsetattr(fd,TCSANOW,&options) != 0) { perror("SetupSerial 3"); return (FALSE); } return (TRUE); }
1.4 本地设置,本地模式成员变量c_lflag可以控制串口驱动怎样控制输入字符.通常,你可能需要通过c_lflag成员来设置经典输入和原始输入模式。
1.4.1 选择经典模式,经典输入是以面向行设计的.在经典输入模式中输入字符会被放入一个缓冲之中,这样可以以与用户交互的方式编辑缓冲的内容,直到收到CR(carriage return)或者LF(line feed)字符.选择使用经典输入模式的时候,你通常需要选择ICANON,ECHO和ECHOE选项: options.c_lflag |= (ICANON | ECHO | ECHOE);
1.4.2 选择原始输入,原始输入根本不会被处理.输入字符只是被原封不动的接收.一般情况中,如果要使用原始输入模式,程序中需要去掉ICANON,ECHO,ECHOE和ISIG选项:options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
(2)读写串口,unix将串口当做文件来处理,直接读取串口即可,write函数也会返回发送数据的字节数或者在发生错误的时候返回-1.通常,发送数据最常见的错误就是EIO,当调制解调器或者数据链路将Data Carrier Detect(DCD)信号线弄掉了,就会发生这个错误.而且,直至关闭端口这个情况会一直持续.
char buffer[1024];int Length;int nByte;nByte = write(fd, buffer ,Length)
读取数据:使用文件操作read函数读取,如果设置为原始数据模式(Raw Date Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数,也就是返回从串口输入缓冲区中实际得到的字符的个数.在不能得到数据的情况下,read(2)系统调用就会一直等着,只到有端口上新的字符可以读取或者发生超时或者错误的情况发生.
char buff[1024];int Len;int readByte = read(fd,buff,Len);
如果需要read(2)函数迅速返回的话,可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作: fcntl(fd, F_SETFL, FNDELAY);标志FNDELAY可以保证read()函数在端口上读不到字符的时候返回0.需要回到正常(阻塞)模式的时候,需要再次在不带FNDELAY标志的情况下调用fcntl()函数:
(3)函数编程,需要的头文件如下
#include <stdio.h> /*标准输入输出定义*/ #include <stdlib.h> /*标准函数库定义*/ #include <unistd.h> /*Unix 标准函数定义*/ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /*文件控制定义*/ #include <termios.h> /*PPSIX 终端控制定义*/ #include <errno.h> /*错误号定义*/
一个树莓派发送数据数据给电脑串口的程序如下:需要根据具体的情况修改USB口
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 #include <termios.h> 8 #include <errno.h> 9 #include <string.h> 10 11 //serial port set function 12 void setTermios(struct termios *pNewtio, unsigned short uBaudRate) 13 { 14 bzero(pNewtio,sizeof(struct termios)); 15 pNewtio->c_cflag = uBaudRate|CS8|CREAD|CLOCAL; 16 pNewtio->c_iflag = IGNPAR; 17 pNewtio->c_oflag = 0; 18 pNewtio->c_lflag = 0; 19 pNewtio->c_cc[VINTR] = 0; 20 pNewtio->c_cc[VQUIT] = 0; 21 pNewtio->c_cc[VERASE] = 0; 22 pNewtio->c_cc[VKILL] = 0; 23 pNewtio->c_cc[VEOF] = 4; 24 pNewtio->c_cc[VTIME] = 5; 25 pNewtio->c_cc[VMIN] = 0; 26 pNewtio->c_cc[VSWTC] = 0; 27 pNewtio->c_cc[VSTART] = 0; 28 pNewtio->c_cc[VSTOP] = 0; 29 pNewtio->c_cc[VSUSP] = 0; 30 pNewtio->c_cc[VEOL] = 0; 31 pNewtio->c_cc[VREPRINT] = 0; 32 pNewtio->c_cc[VDISCARD] = 0; 33 pNewtio->c_cc[VWERASE] = 0; 34 pNewtio->c_cc[VLNEXT] = 0; 35 pNewtio->c_cc[VEOL2] = 0; 36 } 37 int main(int argc,char **argv) 38 { 39 int fd; 40 int nCount,nTotal; 41 int i,j,m; 42 int ReadByte = 0; 43 44 int Buffer[512]; 45 struct termios oldtio,newtio; 46 char *dev = "/dev/tq2440_serial0"; 47 48 if((argc!=3)||(sscanf(argv[1],"%d",&nTotal)!=1)) 49 { 50 printf("Usage:COMSend count datat! "); 51 return -1; 52 } 53 54 if((fd=open(dev,O_RDWR|O_NOCTTY|O_NDELAY))<0) //open serial COM2 55 { 56 printf("Can't open serial port! "); 57 return -1; 58 } 59 tcgetattr(fd,&oldtio); 60 setTermios(&newtio,B9600); 61 tcflush(fd,TCIFLUSH); 62 tcsetattr(fd,TCSANOW,&newtio); 63 64 //Send data 65 for(i=0;i<nTotal;i++) 66 { 67 nCount = write(fd,argv[2],strlen(argv[2])); 68 printf("send data OK!count=%d ",nCount); 69 sleep(1); 70 } 71 72 //receive data 73 for(j=0;j<20;j++) 74 { 75 ReadByte = read(fd,Buffer,512); 76 if(ReadByte>0) 77 { 78 printf("readlength=%d ",ReadByte); 79 Buffer[ReadByte]='\0'; 80 printf("%s ",Buffer); 81 sleep(3); 82 } 83 else printf("Read data failure times=%d ",j); 84 } 85 printf("Receive data finished! "); 86 tcsetattr(fd,TCSANOW,&oldtio); 87 close(fd); 88 return 0; 89 }
参考文章:http://blog.sina.com.cn/s/blog_644949120100sc8n.html
http://www.cnblogs.com/jason-lu/articles/3173988.html