嵌入式Linux应用学习(二)------依赖Linux kernel驱动的uart串口应用程序编写

来源:互联网 发布:poser pro 是什么软件 编辑:程序博客网 时间:2024/06/08 11:20

你好!这里是风筝的博客,

欢迎和我一起多多交流。


之前文章讲过uart串口与tty的关系
(http://blog.csdn.net/guet_kite/article/details/76269575),并分析了程序,现在我们基于内核现有的uart驱动来编写uart应用程序。

以kernel4.8.17为例,开发板一上电,启动内核时,会有log打印出来,我们可以知道,uart的驱动kernel已经帮我们加载好了,就在根文件系统/dev/目录下,为ttySAC0、ttySAC1、ttySAC2,因为2440有三个串口。
我们试着:echo hello > ttySAC0
就会发现控制台打印出 hello字样了。
现在我们以串口1为例,在板子com1口上接USB转TTL模块,
打开串口助手,9600波特率,1位停止位,8位数据位,无奇偶校验。
echo hello_uart1 > ttySAC1
就可以在串口助手上接收到 hello_uart1 字样了。
代码如下:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(int argc, char **argv){    int fd;    char val[] = "hello world\n";    fd = open("/dev/ttySAC1", O_RDWR);    if (fd < 0)        printf("can't open dev\n");    else        printf("can open dev\n");    write(fd, val, strlen(val));    return 0;}


但是问题来了,9600波特率是内核默认配置的,我想要115200波特率呢?
这就涉及一个关键的结构体了(在termios.h里):

struct termios{tcflag_t c_iflag; /* 输入选项标志 */tcflag_t c_oflag; /* 输出选项标志 */tcflag_t c_cflag; /* 控制选项标志 */tcflag_t c_lflag; /* 本地选项标志 */unsigned char c_line /*线控制*/cc_t c_cc[NCCS]; /* 控制特性 */};


这个结构中最重要的是c_cflag。通过对它的赋值,用户可以设置波特率、字符大小、数据位、停止位、奇偶校验位和硬件流控等。

其中,获得端口波特率信息是通过cfgetispeed函数和cfgetospeed函数来实现的。 cfgetispeed函数用于获得结构体 termios_p中的输入波特率信息,而cfgetospeed函数用于获得结构体termios_p中的输出波特率信息。
cfsetispeed函数和cfsetospeed函数用于设置端口的输入/输出波特率。一般情况下,输入和输出波特率是相等的。
即:

    struct termios opt;    tcgetattr(fd, &opt);//该函数与设备文件绑定,开始必须要调用    cfsetispeed(&opt, B115200);    cfsetospeed(&opt, B115200);    tcsetattr(fd,TCSANOW,&opt);//该函数与设备文件绑定,最后必须要调用


即可修改波特率为115200。

好了,现在能通过串口1发送数据了,怎么接收呢?
原始数据模式下每个read函数将返回实际串口收到的字符数,如果串口中没有字符可用,回叫将会阻塞直到以下几种情况:有字符进入;一个间隔计时器失效;错误发送。
在打开串口成功后,使用fcntl(fd, F_SETFL, FNDELAY)语句,可以使read函数立即返回而不阻塞。FNDELAY选项使read函数在串口无字符时立即返回且为0。
但是一直read也不好啊。read函数返回的字符数是实际串口收到的字符数。Linux下直接用read读串口可能会造成堵塞,或者数据读出错误

要知道,在linux 下的标准串口,在核心的驱动程序中是通过中断方式处理的,硬件中断是设备驱动层级的,而读写串口是用户级行为,所以在应用程序里,是做不到使用中断的。但是可以采用 select 方式监测“读文件描述符”,实现异步读取。或者是signal信号机制来实现模拟中断。

这里,我们通过select系统调用,在没有数据时阻塞进程,串口有数据需要读时唤醒进程。
下面给出程序,注释已经写好了,
注意的是,select函数最后一个参数为timeval结构体,有两个成员函数,一个为秒,一个为微秒,代表select函数的超时时间,如果赋值为NULL,这代表绝对阻塞,直到有数据出现。如果都设为0,代表立即返回。
还有一个地方就是,不要设置opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);虽然设置了能接收到不带回车的串口数据,但是这样串口一次只能接收八个字节,,,,,,,

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <termios.h>#include <sys/signal.h> #include <unistd.h>  struct termios opt;fd_set uart_read;struct timeval time;int len,read_flag;char rcv_buf[100];//char buf[50];//int pipes[2];int set_uart(int fd,int boud,int flow_ctrl,int databits,int stopbits,int parity) ;int main(int argc, char **argv){    int fd;    char val[] = "hello world\n";    pid_t fpid; //fpid表示fork函数返回的值     /*O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序?    *O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。    */    fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY);//O_NOCTTY:表示打开的是一个终端设备,程序不会成为该端口的控制终端。    if (fd < 0)        printf("can't open dev\n");    else        printf("can open dev\n");    /*测试是否为终端设备 */       if(0 == isatty(STDIN_FILENO))             printf("standard input is not a terminal device/n");    set_uart(fd,115200,0,8,1,'N');    /* 将uart_read清零使集合中不含任何fd*/    FD_ZERO(&uart_read);      /* 将fd加入uart_read集合 */    FD_SET(fd,&uart_read);    /*不阻塞*/    time.tv_sec = 0;          time.tv_usec = 0;     fpid=fork();    if (fpid < 0)           printf("error in fork!");           else if (fpid == 0) //子进程    {        /*if (pipe(pipes)<0)//创建管道        {            printf("error pipe \n");            exit(0);        }*/        while(1)        {            read_flag = select(fd+1,&uart_read,NULL,NULL,NULL);            //select(fd+1,&uart_read,NULL,NULL,&time);            if(read_flag)              {                  memset(rcv_buf,0,sizeof(rcv_buf)/sizeof(char));                        len = read(fd,rcv_buf,sizeof(rcv_buf)/sizeof(char));                      printf("data len = %d\n val = %s\n",len,rcv_buf);                      //write(pipes[1],rcv_buf,strlen(rcv_buf));            }            else            {                printf(" receive  data error ! \n");            }            usleep(10000);        }       }      write(fd, val, strlen(val));    while(1)    {        //read(pipes[0],buf,sizeof(buf)/sizeof(char));//读出字符串,并将其储存在char s[]中        //printf("data val is %s\n",buf);//打印字符串    }    close(fd);    return 0;}int set_uart(int fd,int boud,int flow_ctrl,int databits,int stopbits,int parity)  {     int i;    int baud_rates[] = { B9600, B19200, B57600, B115200, B38400 };    int speed_rates[] = {9600, 19200, 57600, 115200, 38400};    /*得到与fd指向对象的相关参数,并保存*/    if (tcgetattr(fd, &opt) != 0)    {        printf("fail get fd data \n");        return -1;    }    for ( i= 0;  i < sizeof(speed_rates) / sizeof(int);  i++)      {          if (boud == speed_rates[i])          {                           cfsetispeed(&opt, baud_rates[i]);               cfsetospeed(&opt, baud_rates[i]);            }      }     /*修改控制模式,保证程序不会占用串口*/          opt.c_cflag |= CLOCAL;          /*修改控制模式,使得能够从串口中读取输入数据 */         opt.c_cflag |= CREAD;    /*设置数据流控制 */         switch(flow_ctrl)          {          case 0 ://不使用流控制                  opt.c_cflag &= ~CRTSCTS;                  break;             case 1 ://使用硬件流控制                  opt.c_cflag |= CRTSCTS;                  break;          case 2 ://使用软件流控制                  opt.c_cflag |= IXON | IXOFF | IXANY;                  break;          default:                         printf("Unsupported mode flow\n");                      return -1;        }    /*屏蔽其他标志位  */        opt.c_cflag &= ~CSIZE;    /*设置数据位*/    switch (databits)          {            case 5    :                       opt.c_cflag |= CS5;                       break;          case 6    :                       opt.c_cflag |= CS6;                       break;          case 7    :                          opt.c_cflag |= CS7;                      break;          case 8:                          opt.c_cflag |= CS8;                      break;            default:                         printf("Unsupported data size\n");                      return -1;           }     /*设置校验位 */      switch (parity)          {            case 'n':          case 'N': //无奇偶校验位。                      opt.c_cflag &= ~PARENB;                       opt.c_iflag &= ~INPCK;                          break;           case 'o':            case 'O'://设置为奇校验                           opt.c_cflag |= (PARODD | PARENB);                        opt.c_iflag |= INPCK;                                   break;           case 'e':           case 'E'://设置为偶校验                     opt.c_cflag |= PARENB;                              opt.c_cflag &= ~PARODD;                              opt.c_iflag |= INPCK;                             break;              case 's':          case 'S': //设置为空格                       opt.c_cflag &= ~PARENB;                      opt.c_cflag &= ~CSTOPB;                      break;               default:                        printf("Unsupported parity\n");                          return -1;        }     /* 设置停止位 */          switch (stopbits)          {            case 1:                         opt.c_cflag &= ~CSTOPB;            break;           case 2:                         opt.c_cflag |= CSTOPB;             break;          default:                          printf("Unsupported stop bits\n");                          return -1;          }    /*设置使得输入输出时没接收到回车或换行也能发送*/    //opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);    opt.c_oflag &= ~OPOST;//原始数据输出    /*下面两句,区分0x0a和0x0d,回车和换行不是同一个字符*/        opt.c_oflag &= ~(ONLCR | OCRNL);     opt.c_iflag &= ~(ICRNL | INLCR);    /*使得ASCII标准的XON和XOFF字符能传输*/        opt.c_iflag &= ~(IXON | IXOFF | IXANY);    //不需要这两个字符    /*如果发生数据溢出,接收数据,但是不再读取*/    tcflush(fd, TCIFLUSH);    /*如果有数据可用,则read最多返回所要求的字节数。如果无数据可用,则read立即返回0*/        opt.c_cc[VTIME] = 0;        //设置超时        opt.c_cc[VMIN] = 0;        //Update the Opt and do it now    /*    *使能配置    *TCSANOW:立即执行而不等待数据发送或者接受完成    *TCSADRAIN:等待所有数据传递完成后执行    *TCSAFLUSH:输入和输出buffers  改变时执行    */    if (tcsetattr(fd,TCSANOW,&opt) != 0)    {        printf("uart set error!\n");                  return -1;     }    return 0;}


注意:控制符VTIME和VMIN之间有复杂的关系。VTIME定义要求等待的时间(百毫米,通常是unsigned char变量),而VMIN定义了要求等待的最小字节数(相比之下,read函数的第三个参数指定了要求读的最大字节数)。
如果VTIME=0,VMIN=要求等待读取的最小字节数,read必须在读取了VMIN个字节的数据或者收到一个信号才会返回。
如果VTIME=时间量,VMIN=0,不管能否读取到数据,read也要等待VTIME的时间量。
如果VTIME=时间量,VMIN=要求等待读取的最小字节数,那么将从read读取第一个字节的数据时开始计时,并会在读取到VMIN个字节或者VTIME时间后返回。
如果VTIME=0,VMIN=0,不管能否读取到数据,read都会立即返回。

还有一种方式是用signal机制来接收数据的,不过感觉不怎么好用:

struct sigaction saio;int wait_flag = 0;  int res;void  signal_handler_IO (int status)  {      printf ("received SIGIO signale.\n");      wait_flag = 0;  }int main(int argc, char **argv){    int fd;    char val[] = "hello world\n";    pid_t fpid; //fpid表示fork函数返回的值     /*O_NOCTTY:告诉Unix这个程序不想成为“控制终端”控制的程序,不说明这个标志的话,任何输入都会影响你的程序?    *O_NDELAY:告诉Unix这个程序不关心DCD信号线状态,即其他端口是否运行,不说明这个标志的话,该程序就会在DCD信号线为低电平时停止。    */    fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY);//O_NOCTTY:表示打开的是一个终端设备,程序不会成为该端口的控制终端。    if (fd < 0)        printf("can't open dev\n");    else        printf("can open dev\n");    /*测试是否为终端设备 */       if(0 == isatty(STDIN_FILENO))             printf("standard input is not a terminal device/n");    set_uart(fd,115200,0,8,1,'N');    saio.sa_handler = signal_handler_IO;      sigemptyset (&saio.sa_mask);      saio.sa_flags = 0;      saio.sa_restorer = NULL;      sigaction (SIGIO, &saio, NULL);    //allow the process to receive SIGIO      fcntl (fd, F_SETOWN, getpid ());      //make the file descriptor asynchronous      fcntl (fd, F_SETFL, FASYNC);     while(1)    {            usleep(100000);            /* after receving SIGIO ,wait_flag = 0,input is availabe and can be read*/            if(wait_flag == 0)            {                memset(rcv_buf,0,15);                res = read(fd,rcv_buf,15);                printf("nread=%d,%s\n",res,rcv_buf);                wait_flag = 1; /*wait for new input*/            }    }    return  0;}

最后,给出一些设置参数:
c_cflag用于设置控制参数,除了波特率外还包含以下内容:
EXTA External rate clock
EXTB External rate clock
CSIZE Bit mask for data bits
CS5 5个数据位
CS6 6个数据位
CS7 7个数据位
CS8 8个数据位
CSTOPB 2个停止位(清除该标志表示1个停止位
PARENB 允许校验位
PARODD 使用奇校验(清除该标志表示使用偶校验)
CREAD Enable receiver
HUPCL 关闭设备时挂起
CLOCAL 忽略调制解调器线路状态
LOBLK Block job control outpu
c_cflag标志可以定义CLOCAL和CREAD,这将确保该程序不被其他端口控制和信号干扰,同时串口驱动将读取进入的数据。CLOCAL和CREAD通常总是被是能的。

c_lflag用于设置本地模式,决定串口驱动如何处理输入字符,设置内容如下:
ISIG 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
ICANON 使用标准输入模式
XCASE 在ICANON和XCASE同时设置的情况下,终端只使用大写。如果只设置了XCASE,则输入字符将被转换为小写字符,除非字符使用了转义字符(非POSIX,且Linux不支持该参数)
ECHO 显示输入字符
ECHOE 如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词
ECHOK 如果ICANON同时设置,KILL将删除当前行
ECHONL 如果ICANON同时设置,即使ECHO没有设置依然显示换行符
NOFLSH Disable flushing of input buffers after interrupt or quit characters
IEXTEN Enable extended functions
ECHOCTL Echo control characters as ^char and delete as ~?
ECHOPRT 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
ECHOKE BS-SP-BS entire line on line kill
FLUSHO Output being flushed
PENDIN Retype pending input at next read or input char
TOSTOP 向后台输出发送SIGTTOU信号

c_iflag用于设置如何处理串口上接收到的数据,包含如下内容:
IGNBRK 忽略BREAK键输入
BRKINT 如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断
IGNPAR 忽略奇偶校验错误
PARMRK 标识奇偶校验错误
INPCK 允许输入奇偶校验
ISTRIP 去除字符的第8个比特
INLCR 将输入的NL(换行)转换成CR(回车)
IGNCR 忽略输入的回车
ICRNL 将输入的回车转化成换行(如果IGNCR未设置的情况下)
IUCLC 将输入的大写字符转换成小写字符(非POSIX)
IXON 允许输入时对XON/XOFF流进行控制
IXANY 输入任何字符将重启停止的输出
IXOFF 允许输入时对XON/XOFF流进行控制
IMAXBEL 当输入队列满的时候开始响铃,linux在使用该参数而是认为该参数总是已经设置

c_oflag用于设置如何处理输出数据,包含如下内容:
OPOST 处理后输出
OLCUC 将输入的小写字符转换成大写字符(非POSIX)
ONLCR 将输入的NL(换行)转换成CR(回车)及NL(换行)
OCRNL 将输入的CR(回车)转换成NL(换行)
ONOCR 第一行不输出回车符
ONLRET 不输出回车符
OFILL 发送填充字符以延迟终端输出
OFDEL 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘\0’)(非POSIX)
NLDLY 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
NL0 No delay for NLs
NL1 Delay further output after newline for 100 milliseconds
CRDLY 回车延迟,取值范围为:CR0、CR1、CR2和 CR3 column
CR0 No delay for CRs
CR1 Delay after CRs depending on current column position
CR2 Delay 100 milliseconds after sending CRs
CR3 Delay 150 milliseconds after sending CRs
TABDLY 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
TAB0 No delay for TABs
TAB1 Delay after TABs depending on current column position
TAB2 Delay 100 milliseconds after sending TABs
TAB3 Expand TAB characters to spaces
BSDLY 空格输出延迟,可以取BS0或BS1
BS0 No delay for BSs
BS1 Delay 50 milliseconds after sending BSs
VTDLY 垂直制表符输出延迟,可以取VT0或VT1
VT0 No delay for VTs
VT1 Delay 2 seconds after sending VTs
FFDLY 换页延迟,可以取FF0或FF1
FF0 No delay for FFs
FF1 Delay 2 seconds after sending FFs

c_cc定义了控制字符,包含以下内容:
VINTR Interrupt字符
VQUIT Quit CTRL-Z
VERASE Erase Backspace (BS)
VKILL Kill-line CTRL-U
VEOF End-of-file CTRL-D
VEOL 附加的End-of-file字符
VEOL2 Second end-of-line Line feed (LF)
VMIN 非规范模式读取时的最小字符数
VSTART Start字符
VSTOP Stop字符
VTIME 非规范模式读取时的超时时间

原创粉丝点击