原创 Linux下串口程序开发------配置、读取串口及实例★★★★★

2011-6-14 21:18 9406 7 9 分类: MCU/ 嵌入式

Linux下串口程序开发

串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是RS-232-C接口(又称EIA RS-232-C)它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个25个脚的DB25连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于4%的情况下,传输电缆长度应为<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />50英尺<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

linux文件中,所有的设备文件一般都位于/dev下,其中串口一、串口二分别对应的设备名依次为“/dev/ttyS0、“/dev/ttyS1,可以查看在/dev下的文件以确认。在linux下面对于串口的读写就可以通过简单的readwrite函数来完成,所不同的是只是需要对串口的其他参数另坐配置。

1.   串口编程需要用到的头文件

#include <stdio.h> // 标准输入输出定义

#include <stdlib.h>

#include <fcntl.h> // 文件控制定义,主要完成串口通信中对文件的读写操作

#include <unistd.h> // linux标准函数定义

#include <sys/ioctl.h>

#include <termios.h>   // POSIX终端控制定义                        

#include <sys/time.h> 

#include <sys/types.h> 

2.   串口终端函数

2.1      打开串口设备

     int fd

       char *device = "/dev/tts/0";                   // 设备路径,初始使用UART0

      

       for(t=1;t<argc;t++)                                // 获取程序入口时输入的参数

       {

              if(!strcmp(argv[t],"-d") && (argc > (t+1)))

              {

                     device = argv[t+1];

              }

       }

       if(!strcmp(device,"/dev/tts/1"))               // 不允许使用UART1,因为它已和PC相连。

       {

              printf("can not use /dev/tts/1\n");

              return -1;     

       }

       fd = open(device, O_RDWR);                         // 打开设备

       if (fd < 0)                                          // 设备打开失败

       {

              printf("open device error\n");

              return -1;

       }

2.2      设置串口

最基本的串口设置包括波特率设置,校验位和停止位设置。实际上串口的设置只要是设置Struct termios()结构体中各成员的值。

struct termio

{     unsigned short  c_iflag;    /* 输入模式标志 */

       unsigned short  c_oflag;          /* 输出模式标志 */

       unsigned short  c_cflag;          /* 控制模式标志*/ 

       unsigned short  c_lflag;           /* local mode flags */

       unsigned char  c_line;                  /* line discipline */      

       unsigned char  c_cc[NCC];    /* control characters */

};

2.2.1  波特率设置

struct  termios Opt;

tcgetattr(fd, &Opt);             // 得到当前串口的参数

cfsetispeed(&Opt,B19200);     /*设置为19200Bps*/

cfsetospeed(&Opt,B19200);

tcsetattr(fd,TCANOW,&Opt);    // 激活新配置

设置波特率的例子函数:

/**

*@brief  设置串口通信速率

*@param  fd     类型 int  打开串口的文件句柄

*@param  speed  类型 int  串口速度

*@return  void

*/

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(fd1, TCSANOW, &Opt); 

                     if  (status != 0) {       

                            perror("tcsetattr fd1"); 

                            return;    

                     }   

                     tcflush(fd,TCIOFLUSH);  

              } 

       }

}

2.2.2  校验位和停止位设置

/**

*@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 */

        break; 

case 'o':  

case 'O':    

        options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/ 

        options.c_iflag |= INPCK;  // INPCK:奇偶校验使能

        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':    // Space 校验

case 's':  /*as no parity*/  

     options.c_cflag &= ~PARENB;

        options.c_cflag &= ~CSTOPB;

        options.c_iflag |= INPCK;

break; 

default:  

        fprintf(stderr,"Unsupported parity\n");   

        return (FALSE); 

} 

 

// 设置停止位 

switch (stopbits)

{  

  case 1:   

         options.c_cflag &= ~CSTOPB;  // 1个停止位

         break; 

  case 2:   

         options.c_cflag |= CSTOPB;  // 2个停止位

     break;

  default:   

          fprintf(stderr,"Unsupported stop bits\n"); 

          return (FALSE);

}

 

// 处理未接收的字符

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); 

}

2.2.3  模式设置

需要注意的是,如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通信。

       newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始数据输入

       newtio.c_oflag &= ~(OPOST); // 原始数据输出

2.2.4  串口配置实例

void init_ttyS(int fd)

{

       struct termios newtio;

 

       bzero(&newtio, sizeof(newtio));

// 得到当前串口的参数

       tcgetattr(fd, &newtio);             

                    

// 将输入波特率设为19200

// 将输出波特率设为19200

       cfsetispeed(&newtio, B19200);                           

       cfsetospeed(&newtio, B19200);            

             

// 使能接收并使能本地状态

       newtio.c_cflag |= (CLOCAL | CREAD);                  

 

// 无校验 8位数据位1位停止位

       newtio.c_cflag &= ~PARENB;                        

       newtio.c_cflag &= ~CSTOPB;

       newtio.c_cflag &= ~CSIZE;

 

// 8个数据位

       newtio.c_cflag |= CS8;

 

// 原始数据输入

       newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

       newtio.c_oflag &= ~(OPOST);

 

// 设置等待时间和最小接收字符数

       newtio.c_cc[VTIME]    = 0;                         

       newtio.c_cc[VMIN]     = 0;  

 

// 处理未接收的字符

       tcflush(fd, TCIFLUSH);                            

 

// 激活新配置

       tcsetattr(fd,TCSANOW,&newtio);                        

}

 

2.1      串口

读取串口数据使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。

char  buff[1024];

int    Len;

int  readByte = read(fd,buff,Len);

可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。

void SERIAL_RX(void)

{

      //  read(fd, RXBUF , RX_len);

#if 1

       int ret,n,pos,retval;

       fd_set rfds;

       struct timeval tv ;

       pos = 0;//指向接收缓冲

 

       for(n = 0; n < RX_len; n++)

       {

              RXBUF[n] = 0xFF;

       }

 

       FD_ZERO(&rfds);// 清空串口接收端口集

       FD_SET(fd,&rfds);// 设置串口接收端口集   

       tv.tv_sec = 2;

       tv.tv_usec = 0;

 

       while(FD_ISSET(fd,& rfds)) // 检测串口是否有读写动作

       {    

// 每次循环都要清空,否则不会检测到有变化

              FD_ZERO(&rfds);// 清空串口接收端口集

              FD_SET(fd,&rfds);// 设置串口接收端口集   

 

              retval = select(fd+1,&rfds,NULL,NULL,&tv);  

              if(retval == -1)

              {

                     perror("select()");

                     break;

              }                             

              else if(retval)

              {  //判断是否还有数据

                     //sleep(2);                             

                     ret = read(fd, RXBUF, RX_len);

                     pos += ret;

                     //printf("ret = %d \n",ret);

                     if((RXBUF[pos-2] == '\r') & (RXBUF[pos-1] == '\n')) // 确实接收到了数据,并打印出来

                     {

                            FD_ZERO(&rfds);

                            FD_SET(fd,&rfds);      

                            retval = select(fd+1,&rfds,NULL,NULL,&tv);  

                            if(!retval)//no datas

                            {

                                   break;

                            }      

                     }

              }

              else

              {

                     break;

              }

       }

}

Linux下直接用read串口可能会造成堵塞,或数据读出错误。然而用select先查询com口,再用read去读就可以避免,并且当com口延时时,程序可以退出,这样就不至于由于com口堵塞,程序就死了。

Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明)

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

先说明两个结构体:

l        struct fd_set

可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如:

?        FD_ZERO(fd_set *set):清除一个文件描述符集;

?        FD_SET(int fd, fd_set *set):将一个文件描述符加入文件描述符集中;

?        FD_CLR(int fd, fd_set *set):将一个文件描述符从文件描述符集中清除;

?        FD_ISSET(int fd, fd_set *set): 检查集合中指定的文件描述符是否可以读写。

一会儿举例说明。

l        struct timeval

是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

struct timeval{

              long tv_sec;

             long tv_unsec;

}

具体解释select的参数:

l        int maxfdp

是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

l        fd_set *readfds

是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

l        fd_set *writefds

是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

l        fd_set *errorfds

同上面两个参数的意图,用来监视文件错误异常。

l        struct timeval* timeout

select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即selecttimeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

l        返回值:

负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件

在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。

一般来说,在使用select函数之前,首先使用FD_ZEROFD_SET来初始化文件描述符集,在使用了select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关的文件描述符后,使用FD_CLR来清除描述符集。

2.2      串口

读写串口设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。发送数据:

char  buffer[1024];

int    Length;

int    nByte;

nByte = write(fd, buffer ,Length);

2.3      关闭串口

closefd);

3.   具体应用实例

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include<time.h>

#include <termios.h>                            

#include <sys/select.h>  

#include <sys/types.h> 

//#include "COMM.h"

 

 

#define ACK_SUCCESS             0x00      // 操作成功

#define  ACK_FAIL             0x01     // 操作失败

#define  ACK_FULL            0x04     //  Full

#define ACK_NOUSER        0x05      // 无此用户

#define  ACK_USER_EXIST 0x07      // 用户已存在

#define  ACK_TIMEOUT             0x08      // 采集超时

#define ACK_COUNT          0x3   //发生错误时候,重试次数

 

extern unsigned char TXBUF[9900];

extern unsigned char RXBUF[9900];

extern unsigned char rev_ok;

extern unsigned int  TX_len;

extern unsigned int  RX_len;

extern unsigned char one_onecontrast(unsigned char user_number_h,unsigned char user_number_l);

extern unsigned char one_morecontrast(void);

extern unsigned char Get_UserNumber_Right(void);

extern unsigned char set_addmode(unsigned char yn_repeat);

extern unsigned char add_Fingerprint(unsigned char time_number,unsigned char user_number_h,

                                                        unsigned char user_number_l,unsigned char user_right);

extern unsigned char del_alluser(void);

extern unsigned char del_oneuser(unsigned char user_number_h,unsigned char user_number_l);

extern unsigned char read_usernumber(void);

 

 

int fd;

 

unsigned char USER_NUMBER_H;    // 用户号高8

unsigned char USER_NUMBER_L;     // 用户号低8

unsigned char USER_RIGHT;            // 用户权限

 

unsigned char FEATURE_BUFFER[512];   // 要下传的指纹特征值数据

unsigned char FEATURE_LEN;                 // 要下传的指纹特征值的长度

 

 

unsigned char CharToHex(unsigned char ch);

unsigned char select_one_onecontrast(void);

void select_Get_Usernumber_right(void);

 

 

/*********************************************************************************************

* name:        SERIAL_TX

* func:          发生数据到指纹模块串口

* para:          none

* ret:            none

* modify:

* comment:         

*********************************************************************************************/

int SERIAL_TX(void)

{

       int ret;

 

       ret = write(fd, TXBUF, TX_len);        // 试图从串口发送数                               

       if(ret == -1)                        // 确实接收到了数据,并打印出来

       {

       //     *(rcv_buf+ret)='\0';

              printf(" Write device error!\n");

              return -1;

       //     ret = 0;

       }

 

       return 0;

}

 

/*********************************************************************************************

* name:        SERIAL_RX

* func:          从指纹模块串口接收数据

* para:          none

* ret:            none

* modify:

* comment:         

*********************************************************************************************/

void SERIAL_RX(void)

{

      //  read(fd, RXBUF , RX_len);

#if 1

       int ret,n,pos,retval;

       fd_set rfds;

       struct timeval tv ;

       pos = 0;//指向接收缓冲

       tv.tv_sec = 2;

       tv.tv_usec = 0;

       for(n = 0; n < RX_len; n++)

       {

              RXBUF[n] = 0xFF;

       }

      

       //while(FD_ISSET(fd,&uart_r)||FD_ISSET(fd,&uart_w));      // 检测串口是否有读写动作

       while(1) // 检测串口是否有读写动作

       {    

              FD_ZERO(&rfds);// 清空串口接收端口集

              FD_SET(fd,&rfds);// 设置串口接收端口集   

              retval = select(fd+1,&rfds,NULL,NULL,&tv);  

              if(retval == -1)

              {

                     perror("select()");

                     break;

              }                             

              else if(retval)

              {  //判断是否还有数据

                     //sleep(2);                             

                     ret = read(fd, RXBUF, RX_len);

                     pos += ret;

                     //printf("ret = %d \n",ret);

                     if((RXBUF[pos-2] == '\r') & (RXBUF[pos-1] == '\n'))                           // 确实接收到了数据,并打印出来

                     {

                            FD_ZERO(&rfds);

                            FD_SET(fd,&rfds);      

                            retval = select(fd+1,&rfds,NULL,NULL,&tv);  

                            if(!retval)//no datas

                            {

                                   break;

                            }      

                     }

              }

              else

              {

                     break;

              }

       }

}

 

void init_ttyS(int fd)

{

       struct termios newtio;

 

       bzero(&newtio, sizeof(newtio));

       tcgetattr(fd, &newtio);                                   // 得到当前串口的参数

       cfsetispeed(&newtio, B19200);                            // 将输入波特率设为19200

       cfsetospeed(&newtio, B19200);                           // 将输出波特率设为19200

       newtio.c_cflag |= (CLOCAL | CREAD);                   // 使能接收并使能本地状态

       newtio.c_cflag &= ~PARENB;                         // 无校验 8位数据位1位停止位

       newtio.c_cflag &= ~CSTOPB;

       newtio.c_cflag &= ~CSIZE;

       newtio.c_cflag |= CS8;

       newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始数据输入

       newtio.c_oflag &= ~(OPOST);

 

       newtio.c_cc[VTIME]    = 0;                          // 设置等待时间和最小接收字符数

       newtio.c_cc[VMIN]     = 0;  

 

       tcflush(fd, TCIFLUSH);                             // 处理未接收的字符

       tcsetattr(fd,TCSANOW,&newtio);                         // 激活新配置

}

 

unsigned char set_addmode(unsigned char yn_repeat)

{

       unsigned char check, i;

       TXBUF[0] = 0xF5;

       TXBUF[1] = 0x2D;

       TXBUF[2] = 0x00;

       TXBUF[3] = yn_repeat;

       TXBUF[4] = 0x00;

       TXBUF[5] = 0x00;

       check = TXBUF[1];

       for (i = 2; i < 6; i++)

       {

              check ^= TXBUF;

       }

       TXBUF[6] = check;

       TXBUF[7] = 0xF5;

      

       rev_ok = 1;

       TX_len = 8;

       RX_len = 8;

 

       SERIAL_TX();

       sleep(delaytime);

       SERIAL_RX();

       rev_ok = RXBUF[4];

 

       return (rev_ok);

}

 

int main(int argc, char ** argv)

{

       int t,ret;

       char mode;

 

       char *device = "/dev/tts/0";                          // 设备路径,初始使用UART0

      

       for(t=1;t<argc;t++)                                // 获取程序入口时输入的参数

       {

              if(!strcmp(argv[t],"-d") && (argc > (t+1)))

              {

                     device = argv[t+1];

              }

       }

       if(!strcmp(device,"/dev/tts/1"))               // 不允许使用UART1,因为它已和PC相连。

       {

              printf("can not use /dev/tts/1\n");

              return -1;     

       }

       fd = open(device, O_RDWR);                         // 打开设备

      

       if (fd < 0)                                          // 设备打开失败

       {

              printf("open device error\n");

              return -1;

       }

      

       init_ttyS(fd);                                   // 初始化设备

 

       while(1)

       {    

              set_addmode(1);                                                  // 设置指纹添加模式,禁止重复

       }    

       close(fd);                                                // 关闭打开的设备

       return 0;                                                 // 正常返回

}

文章评论2条评论)

登录后参与讨论

用户1631309 2016-5-4 21:20

评论是对思考最好的总结…

用户377235 2013-1-29 20:31

谢谢分享!
相关推荐阅读
xuyaosong 2012-11-28 14:24
Lesson 4 4:MATLAB - FFT and Zero Padding
http://blinkdagger.com/matlab/matlab-fft-and-zero-padding This is the fourth post in the blinkdag...
xuyaosong 2012-11-28 13:54
C语言中 多个源文件之间函数如何调用
首先要建立一个头文件,以.h保存 这样 #include typedef struct { char name[100][60]; char number[100][8]; int m...
xuyaosong 2012-11-28 13:53
小波变换尺度相关性去噪程序
所实现的相关性去噪函数为function [s1 a d] = SSNF(s, n, h, g, g1),具体的实现步骤为: 1) 调用离散二进小波分解函数对信号进行分解,得到逼近系数a 和细节...
xuyaosong 2012-11-28 13:29
功率谱密度幅值的具体含义??
http://www.chinavib.com/forum/thread-17307-1-48.html 求信号功率谱时候用下面的不同方法,功率谱密度的幅值大小相差很大! 我的问题是,计算具体...
xuyaosong 2012-01-12 18:38
声明函数指针并实现回调
程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。 ...
xuyaosong 2012-01-12 18:36
如何不用访问地址的方式来编写并口程序 ★★★★★
兄弟我在精华发现了一些关于并口打印的文章,但是都是用inb outb操作的,不知道各路高人能否给小弟一个用open write ioctl read close控制的判断状态并读写的例子,尤...
我要评论
2
7
关闭 站长推荐上一条 /2 下一条