浅谈linux下的串口通讯开发

2019-10-13 11:20:14王冬梅


NOTES 注意

Unix V7 以及很多后来的系统有一个波特率的列表,在十四个值 B0, ..., B9600 之后可以看到两个常数 EXTA, EXTB ("External A" and "External B")。很多系统将这个列表扩展为更高的波特率。

tcsendbreak 中非零的 duration 有不同的效果。SunOS 指定中断 duration*N 秒,其中 N 至少为 0.25,不高于 0.5 。Linux, AIX, DU, Tru64 发送 duration 微秒的 break 。FreeBSD, NetBSD, HP-UX 以及 MacOS 忽略 duration 的值。在 Solaris 和 Unixware 中, tcsendbreak 搭配非零的 duration 效果类似于 tcdrain。 

所有的范例来源自 miniterm.c. The type ahead 暂存器被限制在 255 个字元, 就跟标准输入程序的最大字串长度相同 ( 或 ).

参考程序码中的注解它会解释不同输入模式的使用. 我希望这些程序码都能被了解. 标准输入程序的程序范例的注解写得最好, 其它的范例都只在不同于其它范例的地方做注解.

叙述不是很完整, 但可以激励你对这范例做实验, 以延生出合于你所需应用程序的最佳解.

别忘记要把序列埠的权限设定正确 (也就是: chmod a+rw /dev/ttyS1)!

3.1 标准输入程序

#include 
#include 
#include 
#include 
#include 

/* 鲍率设定被定义在 , 这在 被引入 */
#define BAUDRATE B38400      
/* 定义正确的序列埠 */
#define MODEMDEVICE "/dev/ttyS1"
#define _POSIX_SOURCE 1 /* POSIX 系统兼容 */

#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

main()
{
 int fd,c, res;
 struct termios oldtio,newtio;
 char buf[255];
/* 
 开启数据机装置以读取并写入而不以控制 tty 的模式
 因为我们不想程序在送出 CTRL-C 后就被杀掉.
*/
 fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
 if (fd <0) {perror(MODEMDEVICE); exit(-1); }

 tcgetattr(fd,&oldtio); /* 储存目前的序列埠设定 */
 bzero(&newtio, sizeof(newtio)); /* 清除结构体以放入新的序列埠设定值 */

/* 
 BAUDRATE: 设定 bps 的速度. 你也可以用 cfsetispeed 及 cfsetospeed 来设定.
 CRTSCTS : 输出资料的硬件流量控制 (只能在具完整线路的缆线下工作
      参考 Serial-HOWTO 第七节)
 CS8   : 8n1 (8 位元, 不做同位元检查,1 个终止位元)
 CLOCAL : 本地连线, 不具数据机控制功能
 CREAD  : 致能接收字元
*/
 newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
 
/*
 IGNPAR : 忽略经同位元检查后, 错误的位元组
 ICRNL  : 比 CR 对应成 NL (否则当输入信号有 CR 时不会终止输入)
      在不然把装置设定成 raw 模式(没有其它的输入处理)
*/
 newtio.c_iflag = IGNPAR | ICRNL;
 
/*
 Raw 模式输出.
*/
 newtio.c_oflag = 0;
 
/*
 ICANON : 致能标准输入, 使所有回应机能停用, 并不送出信号以叫用程序
*/
 newtio.c_lflag = ICANON;
 
/* 
 初始化所有的控制特性
 预设值可以在 /usr/include/termios.h 找到, 在注解中也有,
 但我们在这不需要看它们
*/
 newtio.c_cc[VINTR]  = 0;   /* Ctrl-c */ 
 newtio.c_cc[VQUIT]  = 0;   /* Ctrl-/ */
 newtio.c_cc[VERASE]  = 0;   /* del */
 newtio.c_cc[VKILL]  = 0;   /* @ */
 newtio.c_cc[VEOF]   = 4;   /* Ctrl-d */
 newtio.c_cc[VTIME]  = 0;   /* 不使用分割字元组的计时器 */
 newtio.c_cc[VMIN]   = 1;   /* 在读取到 1 个字元前先停止 */
 newtio.c_cc[VSWTC]  = 0;   /* '/0' */
 newtio.c_cc[VSTART]  = 0;   /* Ctrl-q */ 
 newtio.c_cc[VSTOP]  = 0;   /* Ctrl-s */
 newtio.c_cc[VSUSP]  = 0;   /* Ctrl-z */
 newtio.c_cc[VEOL]   = 0;   /* '/0' */
 newtio.c_cc[VREPRINT] = 0;   /* Ctrl-r */
 newtio.c_cc[VDISCARD] = 0;   /* Ctrl-u */
 newtio.c_cc[VWERASE] = 0;   /* Ctrl-w */
 newtio.c_cc[VLNEXT]  = 0;   /* Ctrl-v */
 newtio.c_cc[VEOL2]  = 0;   /* '/0' */

/* 
 现在清除数据机线并启动序列埠的设定
*/
 tcflush(fd, TCIFLUSH);
 tcsetattr(fd,TCSANOW,&newtio);

/*
 终端机设定完成, 现在处理输入信号
 在这个范例, 在一行的开始处输入 'z' 会退出此程序.
*/
 while (STOP==FALSE) {   /* 回圈会在我们发出终止的信号后跳出 */
 /* 即使输入超过 255 个字元, 读取的程序段还是会一直等到行终结符出现才停止.
  如果读到的字元组低于正确存在的字元组, 则所剩的字元会在下一次读取时取得.
  res 用来存放真正读到的字元组个数 */
  res = read(fd,buf,255); 
  buf[res]=0;       /* 设定字串终止字元, 所以我们能用 printf */
  printf(":%s:%d/n", buf, res);
  if (buf[0]=='z') STOP=TRUE;
 }
 /* 回存旧的序列埠设定值 */
 tcsetattr(fd,TCSANOW,&oldtio);
}