UART子系统(五) 串口应用编程之回环(下)

简介: UART子系统(五) 串口应用编程之回环

五、RETURN VALUE 返回值


1.cfgetispeed() 返回 termios 结构中存储的输入波特率。

2.cfgetospeed() 返回 termios 结构中存储的输出波特率。

3.其他函数返回:

(1)0: 成功

(2) -1: 失败,

并且为 errno 置值来指示错误。 注意 tcsetattr() 返回成功,如果任何所要求的修改可以实现的话。因此,当进行多重修改时,应当在这个函数之后再次调用 tcgetattr()来检测是否所有修改都成功实现。


1.2 linux串口编程


Linux 操作系统从一开始就对串行口提供了很好的支持,下面就 Linux 下的串行口通讯编程进行简单的介绍。


(一) 串口简介


串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、

调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个

25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4%

的情况下,传输电缆长度应为 50 英尺。


(二)计算机串口的引脚说明


1670936533714.jpg

(三)串口操作


头文件


串口操作需要的头文件

#include     <stdio.h>      /*标准输入输出定义*/
#include     <stdlib.h>     /*标准函数库定义*/
#include     <unistd.h>     /*Unix 标准函数定义*/
#include     <sys/types.h> 
#include     <sys/stat.h>  
#include     <fcntl.h>      /*文件控制定义*/
#include     <termios.h>    /*POSIX 终端控制定义*/
#include     <errno.h>      /*错误号定义*/


打开串口


在 Linux 下串口文件是位于 /dev 下的

串口一 为 /dev/ttyS0


串口二 为 /dev/ttyS1


打开串口是通过使用标准的文件打开函数操作:

int fd;
/*以读写方式打开串口*/
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd){
/* 不能打开串口一*/
perror(" 提示错误!");
}


设置串口


最基本的设置串口包括波特率设置,效验位和停止位设置。 串口的设置主要是设置 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 *
};


设置这个结构体很复杂,我这里就只说说常见的一些设置:


波特率设置


下面是修改波特率的代码:

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[i]) {    
                        tcflush(fd, TCIOFLUSH);    
                        cfsetispeed(&Opt, speed_arr[i]); 
                        cfsetospeed(&Opt, speed_arr[i]);  
                        status = tcsetattr(fd1, TCSANOW, &Opt); 
                        if  (status != 0) {       
                                perror("tcsetattr fd1"); 
                                return;    
                        }   
                        tcflush(fd,TCIOFLUSH);  
                } 
        }
}


效验位和停止位的设置

1670936597465.jpg

设置效验的函数

/**
*@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); 
}


需要注意的是:


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

options.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
options.c_oflag  &= ~OPOST;   /*Output*/


读写串口


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


发送数据

char  buffer[1024];
int    Length;
int    nByte;
nByte = write(fd, buffer ,Length)


读取串口数据


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


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

char  buff[1024];
int    Len;
int  readByte = read(fd,buff,Len);


关闭串口


关闭串口就是关闭文件。

close(fd);


例子


下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件

/**********************************************************************代码说明:使用串口二测试的,发送的数据是字符,
但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。
**********************************************************************/
#define FALSE  -1
#define TRUE   0
/*********************************************************************/
int OpenDev(char *Dev)
{
        int     fd = open( Dev, O_RDWR );         //| O_NOCTTY | O_NDELAY      
        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/ttyS1"; //串口二
        fd = OpenDev(dev);
        set_speed(fd,19200);
        if (set_Parity(fd,8,1,'N') == FALSE)  {
                printf("Set Parity Error\n");
                exit (0);
        }
while (1) //循环读取数据
{  
        while((nread = read(fd, buff, 512))>0)
        {
                printf("\nLen %d\n",nread);
                buff[nread+1] = '\0';  
                printf( "\n%s", buff);  
        }
}
        close(fd); 
        return 0;
}


a. 串口配置完整函数

下面给出了串口配置的完整的函数。通常,为了函数的通用性,通常将常用的选项都在函数中列出,这样可以大大方便以后用户的调试使用。该设置函数如下所示:

int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
      struct termios newtio,oldtio;
/*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/
      if  ( tcgetattr( fd,&oldtio)  !=  0) { 
             perror("SetupSerial 1");
             return -1;
      }
      bzero( &newtio, sizeof( newtio ) );
/*步骤一,设置字符大小*/
      newtio.c_cflag  |=  CLOCAL | CREAD; 
      newtio.c_cflag &= ~CSIZE; 
/*设置停止位*/
      switch( nBits )
      {
      case 7:
             newtio.c_cflag |= CS7;
             break;
      case 8:
             newtio.c_cflag |= CS8;
             break;
      }
/*设置奇偶校验位*/
      switch( nEvent )
      {
      case 'O': //奇数
             newtio.c_cflag |= PARENB;
             newtio.c_cflag |= PARODD;
             newtio.c_iflag |= (INPCK | ISTRIP);
             break;
      case 'E': //偶数
             newtio.c_iflag |= (INPCK | ISTRIP);
             newtio.c_cflag |= PARENB;
             newtio.c_cflag &= ~PARODD;
             break;
      case 'N':  //无奇偶校验位
             newtio.c_cflag &= ~PARENB;
             break;
      }
/*设置波特率*/
switch( nSpeed )
      {
      case 2400:
             cfsetispeed(&newtio, B2400);
             cfsetospeed(&newtio, B2400);
             break;
      case 4800:
             cfsetispeed(&newtio, B4800);
             cfsetospeed(&newtio, B4800);
             break;
      case 9600:
             cfsetispeed(&newtio, B9600);
             cfsetospeed(&newtio, B9600);
             break;
      case 115200:
             cfsetispeed(&newtio, B115200);
             cfsetospeed(&newtio, B115200);
             break;
      case 460800:
             cfsetispeed(&newtio, B460800);
             cfsetospeed(&newtio, B460800);
             break;
      default:
             cfsetispeed(&newtio, B9600);
             cfsetospeed(&newtio, B9600);
            break;
      }
/*设置停止位*/
      if( nStop == 1 )
             newtio.c_cflag &=  ~CSTOPB;
      else if ( nStop == 2 )
             newtio.c_cflag |=  CSTOPB;
/*设置等待时间和最小接收字符*/
      newtio.c_cc[VTIME]  = 0;
      newtio.c_cc[VMIN] = 0;
/*处理未接收字符*/
      tcflush(fd,TCIFLUSH);
/*激活新配置*/
      if((tcsetattr(fd,TCSANOW,&newtio))!=0)
      {
             perror("com set error");
             return -1;
      }
      printf("set done!\n");
      return 0;
}


b. 打开函数完整函数

下面给出了一个完整的打开串口的函数,同样写考虑到了各种不同的情况。程序如下所示:

/*打开串口函数*/
int open_port(int fd,int comport)
{
       char *dev[]={"/dev/ttyS0","/dev/ttyS1","/dev/ttyS2"};
       long  vdisable;
       if (comport==1)//串口 1
       {    
        fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
            if (-1 == fd){
                   perror("Can't Open Serial Port");
                   return(-1);
            }
       }
       else if(comport==2)//串口 2
       {    
        fd = open( "/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY);
            if (-1 == fd){
                   perror("Can't Open Serial Port");
                   return(-1);
            }
       }
       else if (comport==3)//串口 3
       {
            fd = open( "/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY);
            if (-1 == fd){
                   perror("Can't Open Serial Port");
                   return(-1);
            }
       }
/*恢复串口为阻塞状态*/
       if(fcntl(fd, F_SETFL, 0)<0)
            printf("fcntl failed!\n");
       else
            printf("fcntl=%d\n",fcntl(fd, F_SETFL,0));
/*测试是否为终端设备*/
       if(isatty(STDIN_FILENO)==0)
            printf("standard input is not a terminal device\n");
       else
            printf("isatty success!\n");
       printf("fd-open=%d\n",fd);
       return fd;
}
/*从串口中读取数据*/
int read_datas(int fd, char *rcv_buf,int rcv_wait)
{
       int retval;
       fd_set rfds;
       struct timeval tv;
       int ret,pos;
       tv.tv_sec = rcv_wait;      // wait 2.5s
        tv.tv_usec = 0;
       pos = 0; // point to rceeive buf
       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)
              {// pan duan shi fou hai you shu ju
                        ret = read(fd, rcv_buf+pos, 2048);
                        pos += ret;
                        if (rcv_buf[pos-2] == '\r' && rcv_buf[pos-1] == '\n')
                     {
                                FD_ZERO(&rfds);
                                FD_SET(fd, &rfds);
                                retval = select(fd+1 , &rfds, NULL, NULL, &tv);
                                if (!retval) break;// no datas, break
                        }
                }
              else
              {
                        printf("No data\n");
                        break;
                }
       }
       return 1;
}
/*向串口传数据*/
int send_data(int fd, char *send_buf)
{
    ssize_t ret;
    ret = write(fd,send_buf,strlen(send_buf));
    if (ret == -1)
        {
                printf ("write device %s error\n", DEVICE_TTYS);
                return -1;
        }
    return 1;
}


2. 串口收发实验


本实验用过把串口的发送、接收引脚短接,实现自发自收:使用write函数发出字符,使用read函数读取字符。


2.1 接线


2.1.1 IMX6ULL

1670936732112.jpg

1670936741743.jpg


2.1.2 STM32MP157

1670936749411.jpg


2.2 编程


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
  struct termios newtio,oldtio;
  if ( tcgetattr( fd,&oldtio) != 0) { //获得驱动程序的默认参数放到oldtio中,备份下来
  perror("SetupSerial 1");
  return -1;
  }
  //设置新的参数
  bzero( &newtio, sizeof( newtio ) );//清0,下面设置新的参数
  newtio.c_cflag |= CLOCAL | CREAD; 
  newtio.c_cflag &= ~CSIZE; 
  newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
  newtio.c_oflag  &= ~OPOST;   /*Output*/
  switch( nBits )//设置数据位的个数
  {
  case 7:
  newtio.c_cflag |= CS7;
  break;
  case 8:
  newtio.c_cflag |= CS8;
  break;
  }
  switch( nEvent )//设置校验位
  {
  case 'O':
  newtio.c_cflag |= PARENB;
  newtio.c_cflag |= PARODD;
  newtio.c_iflag |= (INPCK | ISTRIP);
  break;
  case 'E': 
  newtio.c_iflag |= (INPCK | ISTRIP);
  newtio.c_cflag |= PARENB;
  newtio.c_cflag &= ~PARODD;
  break;
  case 'N': 
  newtio.c_cflag &= ~PARENB;
  break;
  }
  switch( nSpeed )//设置波特率
  {
  case 2400:
  cfsetispeed(&newtio, B2400);
  cfsetospeed(&newtio, B2400);
  break;
  case 4800:
  cfsetispeed(&newtio, B4800);
  cfsetospeed(&newtio, B4800);
  break;
  case 9600:
  cfsetispeed(&newtio, B9600);
  cfsetospeed(&newtio, B9600);
  break;
  case 115200:
  cfsetispeed(&newtio, B115200);
  cfsetospeed(&newtio, B115200);
  break;
  default:
  cfsetispeed(&newtio, B9600);
  cfsetospeed(&newtio, B9600);
  break;
  }
  if( nStop == 1 )//设置停止位
  newtio.c_cflag &= ~CSTOPB;
  else if ( nStop == 2 )
  newtio.c_cflag |= CSTOPB;
  newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
  newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: 
                          * 比如VMIN设为10表示至少读到10个数据才返回,
                          * 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
                          * 假设VTIME=1,表示: 
                          *    10秒内一个数据都没有的话就返回
                          *    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
                          */
  tcflush(fd,TCIFLUSH);
  if((tcsetattr(fd,TCSANOW,&newtio))!=0)//tcsetattr函数设置行规程
  {
  perror("com set error");
  return -1;
  }
  //printf("set done!\n");
  return 0;
}
int open_port(char *com)
{
  int fd;
  //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
  fd = open(com, O_RDWR|O_NOCTTY);//打开设备结点,O_NOCTTY
/*
The O_NOCTTY flag tells UNIX that this program doesn't want to be the "controlling terminal" 
for that port. If you don't specify this then any input (such as keyboard abort signals 
and so forth) will affect your process. Programs like getty(1M/8) use this feature 
when starting the login process, but normally a user program does not want this behavior. 
*/
    if (-1 == fd){
  return(-1);
    }
   if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
/*
1.fcntl(fd,F_SETEL,FNDELAY);
 读数据时不等待,没有数据就返回0;
2.fcntl(fd,F_SETFL,0);
 读数据时,没有数据阻塞
*/
   {
    printf("fcntl failed!\n");
    return -1;
   }
   return fd;
}
/*
 * ./serial_send_recv <dev>
 */
int main(int argc, char **argv)
{
  int fd;
  int iRet;
  char c;
  /* 1. open设备节点 */
  /* 2. setup 设置
  * 115200,8N1 波特率
  * RAW mode 行规程
  * return data immediately 即时返回
  */
  /* 3. write and read */
  if (argc != 2)
  {
  printf("Usage: \n");
  printf("%s </dev/ttySAC1 or other>\n", argv[0]);
  return -1;
  }
  fd = open_port(argv[1]);
  if (fd < 0)
  {
  printf("open %s err!\n", argv[1]);
  return -1;
  }
  iRet = set_opt(fd, 115200, 8, 'N', 1);//这就是我要重点关注的,设置比特率位115200,数据位为8,没有校验位,停止位的个数是1。
  if (iRet)
  {
  printf("set port err!\n");
  return -1;
  }
  printf("Enter a char: ");
  while (1)//循环
  {
  scanf("%c", &c);//等待用户输入数据
  iRet = write(fd, &c, 1);//发给指定串口数据
  iRet = read(fd, &c, 1);//读指定串口数据
  if (iRet == 1)//在命令行上打印出来
    printf("get: %02x %c\n", c, c);
  else
    printf("can not get data\n");
  }
  return 0;
}


2.3 上机实验


2.3.1 IMX6ULL


先设置工具链:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin


编译、执行程序:


1. Ubuntu上

arm-buildroot-linux-gnueabihf-gcc -o serial_send_recv serial_send_recv.c


2. 板子上

/mnt/serial_send_recv  /dev/ttymxc5


2.3.2 STM32MP157


先设置工具链:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin


编译、执行程序:

1. Ubuntu上
arm-buildroot-linux-gnueabihf-gcc -o serial_send_recv serial_send_recv.c
2. 板子上
/mnt/serial_send_recv  /dev/ttySTM3
相关文章
|
10月前
|
存储 安全 定位技术
串口,IIC,SPI,USB等总线叙述
串口,IIC,SPI,USB等总线叙述
141 0
|
12月前
|
Linux API 芯片
USB2S可编程USB转串口适配器开发原理的通讯协议
USB2S可编程USB转串口适配器基于FTDI FT2232H芯片设计,对多种通讯协议的支持,包括UART,I2C,SMBus,1-Wire,SPI,CAN和PWM等。
USB2S可编程USB转串口适配器开发原理的通讯协议
|
12月前
|
Web App开发 芯片
USB2S可编程USB转串口适配器的开发原理
USB2S可编程USB转串口适配器的开发原理主要涉及USB接口协议、USB控制器芯片以及串口通信协议等方面。
USB2S可编程USB转串口适配器的开发原理
|
12月前
|
XML 测试技术 网络安全
开发工具:USB转IIC/I2C/SPI/UART适配器模块可编程开发板
总的思路是通过USB或者UART接口发送一些协议字符串,由模块转换成上面几种接口的硬件时序电信号,实现与这几种接口芯片、设备的快速测试。 首先声明一下,大家都是搞硬件开发的,这几种接口当然是很简单的事,但有些时候对于一个新的设备或者芯片的测试,有个现成的工具当然更顺手,节省时间,也更可靠嘛。
uart应用编程
uart应用编程
61 1
uart应用编程
|
Linux API 开发工具
UART子系统(五) 串口应用编程之回环(上)
UART子系统(五) 串口应用编程之回环
281 0
UART子系统(五) 串口应用编程之回环(上)
|
监控 定位技术 数据格式
UART子系统(六) 串口应用编程之GPS定位
UART子系统(六) 串口应用编程之GPS定位
350 0
UART子系统(六) 串口应用编程之GPS定位
UART子系统(一)初识
UART子系统(一)初识
115 0
UART子系统(一)初识
|
定位技术 芯片
UART子系统(二) UART协议层 物理层
UART子系统(二) UART协议层 物理层
299 0
UART子系统(二) UART协议层 物理层
|
C语言 Android开发 芯片
可编程 USB 转串口适配器开发板的接口
可编程 USB 转 UART/I2C/SMBus/SPI/CAN/1-Wire 适配器 USB2S(USB To Serial ports)是多种数字接口物理层协议转发器,自带强大灵活的 S2S 协议固件程序,支持嵌入C 语言程序开发,可实现 Windows/Android/Wince 操作系统USB 接口与串行接口以及串行接口之间的双向通讯,还可用作脉冲计数、数字示波器、电压比较器。广泛应用于电子设备开发、芯片测试、工业数字接口转换、数字接口学习验证等领域。
可编程 USB 转串口适配器开发板的接口