Linux串口通信
扫描二维码
随时随地手机看文章
这儿说的串口包括两种,232和485。其实,二者没有本质的区别,驱动都是一样的,只是232是双工,而485是半双工。
所以,485在正常情况下出于接收状态,一旦需要发送数据时,需要设置对于的IO的状态,使其出于发送状态。
除此,没有太多区别。下面已485为例具体总结。
2 打开串口
fd = open("/dev/s3c2410_serial0", O_RDWR|O_NOCTTY|O_NDELAY);
if (fd < 0)
{
perror("rs485: open 485 device");
return -1;
}
就像打开普通的文件一样操作open。"/dev/s3c2410_serial0":是串口设备名
O_RDWR:以可读可写的方式打开
O_NOCTTY:不是控制终端控制的程序,否则,任何输入都会影响该程序。具体没有测试过,让加就加上呗。
O_NDELAY:不关心DCD信号线状态,即其他端口是否允许。否则,程序会在DCD信号线为低电平时停止。
目前,还不是很明白什么意思。暂时理解为设置为非阻塞状态吧。
3 参数设置
串口的参数放在了结构体struct termios中。
首先,需要获取参数信息。
struct termios options;
if(tcgetattr( fd,&options) != 0)
{
perror("tcgetattr");
return -1;
}
然后,具体设置参数。这块是下面重点说的。我们以9600,8,N,1为例。因为,这是经典的配置。最后,保存新设置的参数。
tcflush(fd,TCIFLUSH);
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("tcsetattr");
return -1;
}
至此,参数设置完毕。3.1 波特率
/*波特率9600*/
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
3.2 数据位
/*数据位8*/options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
设置数据位之前,必须先设置options.c_cflag &= ~CSIZE;
作用是先屏蔽其他标志,然后修改数据位。
3.3 奇偶校验
/*奇偶校验,无*/
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
3.4 停止位
/*停止位,1*/
options.c_cflag &= ~CSTOPB;
3.5 控制设置
/*保证程序不会占用串口,并且可以能够从串口中读取输入数据*/
options.c_cflag |= (CLOCAL|CREAD);
这两个标记必须加上。
3.6 输入输出
options.c_oflag &= ~OPOST;
当OPOST不被使能,c_oflag的其他位也被忽略,效果相当于c_oflag=0
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
3.7 等待时间
/*设置等待时间*/options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 0;
关于这两项设置,内容挺多的,首先VTIME是设置等待时间,最小单位是0.1s;VMIN是设置等待字符数。
有四种情况,具体如下:
VTIME=0,VMIN=0:read时,有数据就读,没有就立马返回。
VTIME=1,VMIN=0:read时,有数据时就读,读的过程中没有延时。没有数据时,会阻塞0.1s,然后返回。
VTIME=0,VMIN=5:read时,有数据时,读到5个字节,立马返回。没有数据时,会一直阻塞着,非得等到那个字节的到来。
VTIME=1,VMIN=5:read时,有数据是,读到一个字节后,才开始计时,然后,如果时间到或已经读取了5个字节,则返回。
没有数据时,会一直阻塞着等待字符的到来,此时时间不起作用,因为还没有计时。
注意:只有在打开设备时,设置为阻塞,即没有设置O_NONBLOCK或O_NDELAY标志,上面的情况才会有效。
上面的1和5只是为了方便说明而已。总体来说,只要设置了VMIN,在没有数据可读时,就会一直阻塞。
4 读串口
关于读串口,网上说了很多方式,有select查询,软中断等。当然,最终实际去读的都是用read函数。
我采取的方式是先用select查询是否有数据需要读,然后,每次读取一个字节,再去用状态机检查该字节。这样一来,不用考虑
每次读取多少,以及是否读全等问题。代码如下:
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
FD_ZERO(&readfd);
FD_SET(fd,&readfd);
ret=select(fd+1,&readfd,NULL,NULL,&timeout);
if(ret > 0)
{
if(FD_ISSET(fd,&readfd))
{
ret = read(fd, &cmd, 1);
if(ret > 0)
{
/*有信息,则去处理信息*/
}
}
}
处理信息函数根据当前状态处理该字节,并进入下一状态。目前,不太确定加上select和直接用来read哪个效率高,有没有必要加select。
5 写串口
按说串口的写只是用一个write函数即可实现的。但是,现在是485串口,写串口时,需首先更改串口状态。
下面重点说明一下如何更改串口状态。
5.1 /sys/class/gpio
它是gpio到文件系统的映射,通过操作里面的文件,可以直接操作gpio。
首先,查看系统中有没有/sys/class/gpio目录。
如果没有,需要在配置内核时加上Device Drivers —> GPIO Support —> /sys/class/gpio/。
5.2 文件介绍
该目录下有export和unexport两个可写文件和其他的类似gpiochip146的软连接。
export:通知系统需要导出控制的GPIO引脚编号。
unexport:通知系统取消导出。
gpiochip146:保存系统中GPIO寄存器的信息。该目录在/sys/devices/virtual/gpio/gpiochip146
目录下有label、base、ngpio、subsystem、uevent。代表的意义如下:
label:设备信息,如cat label,得到的是GPH2
base:设备所管理的gpio初始编号,如cat base,得到的是146
ngpio:设备所管理的gpio总是,如cat ngpio,得到的是8,
即该gpiochip146管理从146到153这8个gpio接口
subsystem:符号链接,指向父目录
uevent:内核与udev(自动设备发现程序)之间的通信接口
5.3 导出GPIO接口
首先,需要计算出引脚编号。
引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数
该引脚即是控制485状态转换的引脚。根据具体的硬件连接来定,我的是146。
fd = open("/sys/class/gpio/export", O_WRONLY);
write(fd, "146", 3);
导出成功后,会在当前目录下生成新的目录gpio146。
5.4 gpio146
该目录下会有几个个文件,其中有value、direction和active_low
direction:具有读写属性,控制GPIO接口的输入输出方向。
如果将"out"写入该文件,该GPIO接口为输出状态;
如果将"in"写入该文件,该GPIO接口为输入状态;
如果将"high"写入该文件,那么在将GPIO接口置为输出状态的同时,也将value的值置为"1";
如果将"low"写入value
当GPIO的方向为输入时,可以通过v文件,那么在将GPIO接口置为输出状态的同时,将"0"写入value文件。
通过对direction文件的读操作还可以判断当前GPIO接口的输入/输出状态("in"/"out")。
value:具有读写属性,表示当前GPIO接口的电平状态。
当GPIO的方向为输入时,可以通过value读出当前GPIO接口的电平状态高低("1"/"0",均以ASCII码表示);
当GPIO方向为输出时,可以向该文件写入"1"/"0",控制当前GPIO接口的高/低电平。
active_low:具有读写属性,值为"0"或"1",用于决定value中的值是否进行翻转。
当值为"0"时,value中的"0"表示低电平,"1"表示高电平;
当值为"1"时,value中的"1"表示低电平,"0"表示高电平。
5.5 输出
设置引脚为输出方向。
fd = open("/sys/class/gpio/gpio146/direction", O_WRONLY);
write(fd, "out", 3);
5.6 发送模式
fd = open("/sys/class/gpio/gpio146/value", O_WRONLY);
write(fd, "1", 1);
此处高低根据硬件情况来。
5.7 取消导出
fd = open("/sys/class/gpio/unexport", O_WRONLY);
write(fd, "146", 3);
5.8 接收模式
发送完之后,需等待一会,然后再设置成接收模式。
这个时间我暂时设置成usleep(1000*len);
len:发送数据的长度。
fd = open("/sys/class/gpio/gpio146/value", O_WRONLY);
write(fd, "0", 1);
6 总结
232的操作比这简单,写时不需要再通过GPIO来更改状态了。