LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 4871|回复: 16

LINUX的串口编程

[复制链接]
发表于 2003-5-29 11:58:15 | 显示全部楼层 |阅读模式
小弟最近在研究LINUX的串口编程,看过了《Linux 程序设计》和《Linux Serial Programming HOW-TO》上的有关串口编程的介绍。我试验着把COM1和COM2直接连接起来。稍微改编了一下lpg中miniterm.c,分成两个程序read_com.c和write_com.c。我分别在tty1和tty2运行这两个程序。令人不解的是这两个程序不能正常的通讯(我的COM1和COM2经测试,同一条线,在WINDOWS下是好的)。另外,为何两个程序的打开的文件描述符都是3?
read_com.c和write_com.c分别如下。望高手指点帮着解决这个问题,并希望再点拨小弟一下有关Linux串口编程的细节(我想用两个串口,每个控制一个设备)。如果有更详细的资料或者是代码请告诉小弟。谢谢!!


  1. /* File: write_com.c */

  2. #include <termios.h>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <sys/signal.h>

  7. #define BAUDRATE B38400
  8. #define MODEMDEVICE "/dev/ttyS1"
  9. #define ENDMINITERM 2                /* ctrl-b to quit miniterm */

  10. #define _POSIX_SOURCE 1                /* POSIX compliant source */

  11. #define FALSE 0
  12. #define TRUE 1

  13. volatile int STOP = FALSE;

  14. void child_handler(int s)
  15. {
  16.     STOP = TRUE;
  17. }

  18. main()
  19. {
  20.     int fd, c;
  21.     struct termios oldtio, newtio, oldstdtio, newstdtio;
  22.     struct sigaction sa;

  23. /*
  24.   Open modem device for reading and writing and not as controlling tty
  25.   because we don't want to get killed if linenoise sends CTRL-C.
  26. */
  27.     fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY);
  28.     if (fd < 0) {
  29.         perror(MODEMDEVICE);
  30.         exit(-1);
  31.     }

  32.     tcgetattr(fd, &oldtio);        /* save current modem settings */

  33. /*
  34.   Set bps rate and hardware flow control and 8n1 (8bit,no parity,1 stopbit).
  35.   Also don't hangup automatically and ignore modem status.
  36.   Finally enable receiving characters.
  37. */
  38.     newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;

  39. /*
  40. Ignore bytes with parity errors and make terminal raw and dumb.
  41. */
  42.     newtio.c_iflag = IGNPAR;

  43. /*
  44. Raw output.
  45. */
  46.     newtio.c_oflag = 0;

  47. /*
  48. Don't echo characters because if you connect to a host it or your
  49. modem will echo characters for you. Don't generate signals.
  50. */
  51.     newtio.c_lflag = 0;

  52. /* blocking read until 1 char arrives */
  53.     newtio.c_cc[VMIN] = 1;
  54.     newtio.c_cc[VTIME] = 0;

  55. /* now clean the modem line and activate the settings for modem */
  56.     tcflush(fd, TCIFLUSH);
  57.     tcsetattr(fd, TCSANOW, &newtio);

  58. /*
  59.   Strange, but if you uncomment this command miniterm will not work
  60.   even if you stop canonical mode for stdout. This is a linux bug.
  61. */
  62.     // tcsetattr(1, TCSANOW, &newtio);        /* stdout settings like modem settings */

  63. /* next stop echo and buffering for stdin */
  64.     tcgetattr(0, &oldstdtio);
  65.     tcgetattr(0, &newstdtio);        /* get working stdtio */
  66.     newstdtio.c_lflag &= ~(ICANON | ECHO);
  67.     tcsetattr(0, TCSANOW, &newstdtio);

  68. /* terminal settings done, now handle in/ouput */
  69.     switch (fork()) {
  70.     case 0:                        /* child */
  71.         /* user input */
  72.         //close(1);                /* stdout not needed */
  73.         printf("Fork child process\n");
  74.         for (c = getchar(); c != ENDMINITERM; c = getchar()) {
  75.             write(fd, &c, 1);
  76.             printf("write %c to fd %d\n", c, fd);
  77.         }
  78.         tcsetattr(fd, TCSANOW, &oldtio);        /* restore old modem setings */
  79.         tcsetattr(0, TCSANOW, &oldstdtio);        /* restore old tty setings */
  80.         close(fd);
  81.         exit(0);                /* will send a SIGCHLD to the parent */
  82.         break;
  83.     case -1:
  84.         perror("fork");
  85.         tcsetattr(fd, TCSANOW, &oldtio);
  86.         close(fd);
  87.         exit(-1);
  88.     default:                        /* parent */
  89.         //close(0);                /* stdin not needed */
  90.         printf("Fork parent process\n");
  91.         sa.sa_handler = child_handler;
  92.         sa.sa_flags = 0;
  93.         sigaction(SIGCHLD, &sa, NULL);        /* handle dying child */
  94.         while (STOP == FALSE) {        /* modem input handler */
  95.             printf("Looping for read...\n");
  96.             read(fd, &c, 1);        /* modem */
  97.             //write(1, &c, 1);        /* stdout */
  98.             printf("receive %c\n");
  99.         }
  100.         wait(NULL);                /* wait for child to die or it will become a zombie */
  101.         break;
  102.     }
  103. }

复制代码

  1. /* File: read_com.c */

  2. #include <termios.h>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <sys/signal.h>

  7. #define BAUDRATE B38400
  8. #define MODEMDEVICE "/dev/ttyS0"
  9. #define ENDMINITERM 2                /* ctrl-b to quit miniterm */

  10. #define _POSIX_SOURCE 1                /* POSIX compliant source */

  11. #define FALSE 0
  12. #define TRUE 1

  13. volatile int STOP = FALSE;

  14. void child_handler(int s)
  15. {
  16.     STOP = TRUE;
  17. }

  18. main()
  19. {
  20.     int fd, c;
  21.     struct termios oldtio, newtio, oldstdtio, newstdtio;
  22.     struct sigaction sa;

  23. /*
  24.   Open modem device for reading and writing and not as controlling tty
  25.   because we don't want to get killed if linenoise sends CTRL-C.
  26. */
  27.     fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY);
  28.     if (fd < 0) {
  29.         perror(MODEMDEVICE);
  30.         exit(-1);
  31.     }

  32.     tcgetattr(fd, &oldtio);        /* save current modem settings */

  33. /*
  34.   Set bps rate and hardware flow control and 8n1 (8bit,no parity,1 stopbit).
  35.   Also don't hangup automatically and ignore modem status.
  36.   Finally enable receiving characters.
  37. */
  38.     newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;

  39. /*
  40. Ignore bytes with parity errors and make terminal raw and dumb.
  41. */
  42.     newtio.c_iflag = IGNPAR;

  43. /*
  44. Raw output.
  45. */
  46.     newtio.c_oflag = 0;

  47. /*
  48. Don't echo characters because if you connect to a host it or your
  49. modem will echo characters for you. Don't generate signals.
  50. */
  51.     newtio.c_lflag = 0;

  52. /* blocking read until 1 char arrives */
  53.     newtio.c_cc[VMIN] = 1;
  54.     newtio.c_cc[VTIME] = 0;

  55. /* now clean the modem line and activate the settings for modem */
  56.     tcflush(fd, TCIFLUSH);
  57.     tcsetattr(fd, TCSANOW, &newtio);

  58. /*
  59.   Strange, but if you uncomment this command miniterm will not work
  60.   even if you stop canonical mode for stdout. This is a linux bug.
  61. */
  62.     // tcsetattr(1, TCSANOW, &newtio);        /* stdout settings like modem settings */

  63. /* next stop echo and buffering for stdin */
  64.     tcgetattr(0, &oldstdtio);
  65.     tcgetattr(0, &newstdtio);        /* get working stdtio */
  66.     newstdtio.c_lflag &= ~(ICANON | ECHO);
  67.     tcsetattr(0, TCSANOW, &newstdtio);

  68. /* terminal settings done, now handle in/ouput */
  69.     switch (fork()) {
  70.     case 0:                        /* child */
  71.         /* user input */
  72.         //close(1);                /* stdout not needed */
  73.         printf("Fork child process\n");
  74.         for (c = getchar(); c != ENDMINITERM; c = getchar()) {
  75.             write(fd, &c, 1);
  76.             printf("write %c to fd %d\n", c, fd);
  77.         }
  78.         tcsetattr(fd, TCSANOW, &oldtio);        /* restore old modem setings */
  79.         tcsetattr(0, TCSANOW, &oldstdtio);        /* restore old tty setings */
  80.         close(fd);
  81.         exit(0);                /* will send a SIGCHLD to the parent */
  82.         break;
  83.     case -1:
  84.         perror("fork");
  85.         tcsetattr(fd, TCSANOW, &oldtio);
  86.         close(fd);
  87.         exit(-1);
  88.     default:                        /* parent */
  89.         //close(0);                /* stdin not needed */
  90.         printf("Fork parent process\n");
  91.         sa.sa_handler = child_handler;
  92.         sa.sa_flags = 0;
  93.         sigaction(SIGCHLD, &sa, NULL);        /* handle dying child */
  94.         while (STOP == FALSE) {        /* modem input handler */
  95.                 printf("Looping for read...\n");
  96.             read(fd, &c, 1);        /* modem */
  97.             //write(1, &c, 1);        /* stdout */
  98.             printf("receive %c\n", c);
  99.         }
  100.         wait(NULL);                /* wait for child to die or it will become a zombie */
  101.         break;
  102.     }
  103. }
复制代码
发表于 2003-5-29 12:09:32 | 显示全部楼层
代码糟糕,没有缩进,看着就皱眉头。
发表于 2003-5-29 12:10:34 | 显示全部楼层
请选择编辑你的帖子,在你的代码的首尾各加上【code】和【/code】。不要用全角字符。
发表于 2003-5-29 13:01:23 | 显示全部楼层
代码缩进我加上了。
每个进程在创建时,都会有3 个文件描述符0,1,2,分别对应stdin,stdout,stderr。如果再打开一个文件,自然就是文件描述符3了。
发表于 2003-5-29 13:30:19 | 显示全部楼层
大致看了一下程序,觉得有几个概念不清楚。
你的read_com.c我write_com.c都是在对一个文件描述符读写。read_com.c是对/dev/ttyS0读写,子进程写入,父进程读取。write_com.c也是一样,只是设备文件变成了/dev/ttyS1。从结构上说,利用设备文件在非同组的进程间通信应该是一个进程写,同时另一个进程读。如果是单向的,可以表示成这样:
进程A:
。。。
打开/dev/ttyS0;
把数据写入/dev/ttyS0;
。。。

进程B:
。。。
打开/dev/ttyS0;
把数据从/dev/ttyS0读出;
。。。
这样可以实现单向的通信。
如果要实现双向的通信,可以在此基础上fork,然后父子进程分别读写,程序的结构就演变成这样:
程序A:
fork()
。。。
如果是父进程,则打开/dev/ttyS0把数据写入/dev/ttyS0;
。。。
如果是子进程,则打开/dev/ttyS1把数据从/dev/ttyS1中读出。
。。。

程序B的做法类似,但读写的文件与程序A正好相反:
程序B:
fork()
。。。
如果是父进程,则打开/dev/ttyS1把数据写入/dev/ttyS1;
。。。
如果是子进程,则打开/dev/ttyS0把数据从/dev/ttyS0中读出。
。。。

从名称上判断,你是希望一个程序写,而另一个程序读。但你写的每个程序都是父子进程打开同一个文件,只能实现同一进程组父子进程间的通信,不能实现与非同组进程之间的通信。问题可能就出在这里了。
 楼主| 发表于 2003-5-29 15:22:19 | 显示全部楼层
谢谢两位斑竹!!!小弟初来乍到,不太懂发帖的规矩,特别是有劳
kj501大哥了。再次真心的感谢!!!

对于kj501大哥的回复,小弟还有几点不懂的地方:

1。对于文件描述符以及0,1,2,我是知道的。只是我不太明白为什么两个程序打开

的为什么相等——都是3?
2。我的实际操作是把我电脑上的COM1和COM2用一条线接起来的(使用2,3,5引脚2

,3用于接受和发送)。关于kj501大哥所说的,进程A进程B进行单向通信,分别打开

/dev/ttyS0,单独操作它,向COM1读写数据,好象意思是根本不需要将COM1和COM2

相连。我不太理解,我认为COM1和COM2要通信,至少要保证物理上是相通的嘛。如果

是写的话,反映到物理上,肯定是通过引脚3发出的吧;如果是从这个串口读的话,

应该是从它的引脚2读入的吧。假如A是写,那么它向引脚3发出电流。这时候的B就在

引脚2等待电流。结果如何呢?我觉得B只能在这里阻塞——因为引脚2和3根本就没有

相连。
3。我原来的想法是,COM1和COM2用一条线接起来,然后write_com向COM2写入字符

,这时的read_com打开COM1,读取这个字符。就是上面的程序。kj501大哥讲的双向

通信的意思好象是读写操作都只需经由一个串口设备文件,也就是一个进程对该文件

写,另一个进程对该文件进行读,这样就可以实现串口通信。对于普通文件,这样应

该是可以的,但是对于串口这样的设备文件,可以吗? (我回去再编码试一试,不

过总觉得不对劲)


我的表达能力不太好,Linux的技术又差。希望kj501大哥和各位大虾们能不嫌我的罗

嗦和麻烦,再拉小弟一把。
另:再次感谢kj501大哥!!!
发表于 2003-5-29 18:43:52 | 显示全部楼层
首先声明,我没有实际从事过串口编程。给出的答复只是为了说明对程序结构的看法。所用的例子确有不当之处,引起了兄弟的误解,在此表示歉意。
1。两个程序的文件描述符都是3,我认为是一种巧合。只能说明设备文件是这两个程序打开的第一个文件。如果其中一个进程先打开了某个文件,然后再打开设备文件,则先打开的文件的描述符就是3,设备文件的描述符自然就是4。你可以在打开/dev/ttyS0之前打开一个普通文件,看看设备文件的描述符会变成多少。
2。和3。由于程序中使用了fork,因此我套用了多进程使用管道的做法来说明程序结构。我在网上搜索一下,没有找到仅仅通过一个串口来实现本地通信的例子。在说明时没有准确理解你的意思,应该是说错了。sorry!
最后,结合本人的经验,谈一点看法。
1。对端口读和写的程序都用到了fork()。比如说读端口的程序read_com.c,父进程读fd,子进程写fd。这就存在一个进程间的同步问题。会不会子进程写入的数据和另一个端口传送过来的数据混合在一起,被父进程读取呢?或者父进程把子进程写入的数据当作另一个端口传送过来的数据读取?我认为使用fork()把问题复杂化了。应该可以不用fork()的。
2。传送的数据最好约定一个格式,也就是要自己定义一个通信协议。这样才好判断缓冲中的数据是要发送的数据还是已经接收的数据。
3。终端设置对于通信是否成功有决定性影响。但这方面的问题确实比较复杂。找了个例子,你参考一下吧:
http://www.linuxforum.net/forum/ ... mp;o=all&fpart=
发表于 2003-5-29 19:23:30 | 显示全部楼层
每个程序的描述符都是独立的
不会互相影响
发表于 2003-5-29 19:27:41 | 显示全部楼层
楼主的程序在这里有说明
可以参考一下

http://www.linuxtested.com/tooltars/keylabs_miniterm_test.txt
发表于 2003-5-29 19:32:49 | 显示全部楼层
原例子是一个程序
运行后会fork一个子进程
然后父子进程间通过串口通信

你去年了两个后可能会乱掉吧
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表