上周的时候,发了篇文章,关于linux串口的【当然程序师转别人的(还是发布在IBM开发者社区的嘞)】,
虽然当时能跑的通,但这周在用的时候却发现不行了,于是决定仔细研究下linux下串口这东西!
当然那个程序有问题,就要自己重新写啦。。。。。。OTZ 。。。。饿。。。我向来比较懒。。。。
当然研究过程中还是明白了不少东西的
1,解决minicom启动后才能正常读取数据的问题
2. 解决读取数据缺失的问题
3. 学习使用select函式检测串口状态信息
。。。。。。。。。。。。。。。。。。。。。。
当然这次顺带加点自己的分析
串口的配置一般也就是
波特率,数据格式这些东西,而linux环境下则通过一
名为termios的结构体对其进行配置
其成员如下
Termios Structure Members
Member
Description
c_cflag
Control options
c_lflag
Line options
c_iflag
Input options
c_oflag
Output options
c_cc
Control characters
c_ispeed
Input baud (new interface)
c_ospeed
Output baud (new interface)
看英文就很容易明白了,所以我也不多说啥了。
这个结构体其实很简单,但其中每一项的配置却要复杂的多,实际上我也没有逐项的学习
只是捡有用的看了下,而下面就是关于这些知识的记录。
1.打开串口
Code
const char * serial_port[]={
"/dev/ttyS0",
"/dev/ttyS1",
"/dev/ttyS2",
"/dev/ttyS3",
"/dev/ttyS4"
};
int serial_open(int port)
{
int fd = open(serial_port[port], O_RDWR|O_NONBLOCK);
if (-1 == fd)
{
perror("Can't Open Serial Port");
exit (-1);
}
else
{
fcntl(fd, F_SETFL, 0);
}
return fd;
} 打开时一般可以用O_RDWR ,O_NOCTTY , O_NDELAY这三个参数的组合
分别代表读写[当然有对应只读,只写的,猜都猜到了],第二个告诉系统该串口不被用来作控制终端【没弄明白,反正一般我是不用,见过跟modem通信的程序会用到】
最后那个就是不要等的意思啦。。。。。。
2.配置串口
下面的漏了个枚举量,先补上
typedef
enum
{
SERIAL_8N1=0,
SERIAL_7E1=1,
SERIAL_7O1=2,
SERIAL_7S1=3
}serial_format;
Code
void serial_format_set(int fd,serial_format format)
{
int status=0;
struct termios options;
if ( tcgetattr( fd,&options) != 0)
{
perror("serial fomat abnormal");
exit (-1);
}
options.c_cflag &= ~CSIZE;
switch (format)
{
case SERIAL_8N1:
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_iflag &= ~(INPCK | ISTRIP);
break;
case SERIAL_7E1:
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
options.c_iflag |= (INPCK | ISTRIP);
break;
case SERIAL_7O1:
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
options.c_iflag |= (INPCK | ISTRIP);
break;
case SERIAL_7S1:
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_iflag &= ~INPCK;
options.c_iflag|=ISTRIP;
break;
default:
perror("serial format abnormal");
exit (-1);
}
tcflush(fd,TCIOFLUSH);
status=tcsetattr(fd, TCSANOW, &options);
if (status != 0)
{
perror("tcsetattr format abnormal");
exit(-1);
}
}
void serial_speed_set(int fd,int baudrate)
{
int status;
struct termios options;
/*
* Get the current options for the port
*/
tcgetattr(fd, &options);
/*
* Set the baud rates to 19200
*/
cfsetispeed(&options, baudrate);
cfsetospeed(&options, baudrate);
/*
* Enable the receiver and set local mode
*/
options.c_cflag |= (CLOCAL | CREAD);
/*
* Set the new options for the port
*/
tcflush(fd,TCIOFLUSH);
status=tcsetattr(fd, TCSANOW, &options);
if (status != 0)
{
perror("tcsetattr speed abnormal");
exit(-1);
}
} speed_set那个自然是用来设置波特率的
tc***attr那个参数用来获取和设置termios的信息【中间的***,自己猜吧。。。XD】
TCSANOW那个参数当然是指立即更新参数的意思
其中对speed设置具体到了输入和输出,也就意味着可以使不同的速率,另外要说的就是如果要设置9600的波特率
在
上面的程序中要传入的参数是B9600,他是一个被定义的数值,不是9600
format_set则是配置串口的数据格式
上面程序中配置了最常见的几种类型
比如8位数据无校验,7位数据的奇校验,偶校验以及空格校验,
下面仅分析8位无校验的最常见情形
//校验除能
options.c_cflag &= ~PARENB;
//此位除能代表一位停止位,反之代表两位停止位
options.c_cflag &= ~CSTOPB;
//清空数据宽度度屏蔽位,以便设置
options.c_cflag &= ~CSIZE;
//设置8位数据宽度
options.c_cflag |= CS8;
//相应的,对于输入的数据也除能校验使能,并且不清除第八位
options.c_iflag &= ~(INPCK | ISTRIP);
上次发的文章中的程序有问题和下面这段代码关系密切
Code
void serial_etc_config(int fd)
{
int status=0;
struct termios options;
if ( tcgetattr( fd,&options) != 0)
{
perror("serial etc abnormal");
exit (-1);
}
//no hardware flow control
options.c_cflag &= ~CRTSCTS;
//no soft control
options.c_iflag &= ~(IXON | IXOFF | IXANY);
//raw set_input
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//no output process , raw set_input
options.c_oflag &=~OPOST;
//min character
options.c_cc[VMIN] = 1;
//time to wait for data
options.c_cc[VTIME] = 15;
tcflush(fd,TCIOFLUSH);
status=tcsetattr(fd, TCSANOW, &options);
if (status != 0)
{
perror("tcsetattr etc abnormal");
exit(-1);
}
} 注释其实写的很明白了,因为linux版的slickedit对中文支持不是很好【配置很麻烦,还容易卡死】,所以用英文了
这里面只说一句,就是
options.c_lflag &=~(ICANON | ECHO | ECHOE | ISIG);
它代表为原始数据传输,而与之相对的则是标准数据传输
其实我也没弄明白到底啥区别,反正就按上面的写才能接收到数据
其实基本的操作都已经罗列完了,但是测试时总会出现这样那样的问题
如果直接用read读取,可能因为种种原因,独到的数据和我们期待的不一致
多半是时序上的问题,
比如你去读时数据还没准备好。。。。
而解决的办法,下面举一例
就是利用ioctl来获取当前串口缓冲中的信息
int
wait=0;do
{
ioctl(fd, FIONREAD, &bytes);
}
while( wait++,bytes!=bytes_needed);
而有些时候若长时间没有相应,我们还要做超时处理
这就要借用select函式了
fd_set input;
struct timeval timeout;
/* Initialize the input set */
FD_ZERO(&input);
FD_SET(fd, &input);
/* Initialize the timeout structure */
timeout.tv_sec = 10;
timeout.tv_usec = 0;
/* Do the select */
n = select(fd, input, NULL, NULL,&timeout) ;
/* See if there was an error */
if (n<0)
perror("select failed");
else if (n == 0)
puts("TIMEOUT");
else
{
/* We have input */
if (FD_ISSET(fd, input))
process_fd();
}
这里要说明的是,超时时间中的usecond对应的是微秒,也就是microsecond
而毫秒的英文是millisecond
另外
FD_ZERO(&input);
FD_SET(fd, &input);
则分别清空了input信号集,并把fd描述符添加至该信号集
哎,别的不多说了,下面给出完整的回传数据测试代码
这次肯定没问题了,因为是自己写的。。。吼吼。。。。OTZ
Code
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/select.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
typedef
enum
{
SERIAL_8N1=0,
SERIAL_7E1=1,
SERIAL_7O1=2,
SERIAL_7S1=3
}serial_format;
const char * serial_port[]={
"/dev/ttyS0",
"/dev/ttyS1",
"/dev/ttyS2",
"/dev/ttyS3",
"/dev/ttyS4"
};
int serial_open(int port)
{
int fd = open(serial_port[port], O_RDWR|O_NONBLOCK);
if (-1 == fd)
{
perror("Can't Open Serial Port");
exit (-1);
}
else
{
fcntl(fd, F_SETFL, 0);
}
return fd;
}
void serial_format_set(int fd,serial_format format)
{
int status=0;
struct termios options;
if ( tcgetattr( fd,&options) != 0)
{
perror("serial fomat abnormal");
exit (-1);
}
options.c_cflag &= ~CSIZE;
switch (format)
{
case SERIAL_8N1:
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_iflag &= ~(INPCK | ISTRIP);
break;
case SERIAL_7E1:
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
options.c_iflag |= (INPCK | ISTRIP);
break;
case SERIAL_7O1:
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS7;
options.c_iflag |= (INPCK | ISTRIP);
break;
case SERIAL_7S1:
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_iflag &= ~INPCK;
options.c_iflag|=ISTRIP;
break;
default:
perror("serial format abnormal");
exit (-1);
}
tcflush(fd,TCIOFLUSH);
status=tcsetattr(fd, TCSANOW, &options);
if (status != 0)
{
perror("tcsetattr format abnormal");
exit(-1);
}
}
void serial_speed_set(int fd,int baudrate)
{
int status;
struct termios options;
/*
* Get the current options for the port
*/
tcgetattr(fd, &options);
/*
* Set the baud rates to 19200
*/
cfsetispeed(&options, baudrate);
cfsetospeed(&options, baudrate);
/*
* Enable the receiver and set local mode
*/
options.c_cflag |= (CLOCAL | CREAD);
/*
* Set the new options for the port
*/
tcflush(fd,TCIOFLUSH);
status=tcsetattr(fd, TCSANOW, &options);
if (status != 0)
{
perror("tcsetattr speed abnormal");
exit(-1);
}
}
void serial_etc_config(int fd)
{
int status=0;
struct termios options;
if ( tcgetattr( fd,&options) != 0)
{
perror("serial etc abnormal");
exit (-1);
}
//no hardware flow control
options.c_cflag &= ~CRTSCTS;
//no soft control
options.c_iflag &=~(IXON | IXOFF | IXANY);
//raw set_input
options.c_lflag &=~(ICANON | ECHO | ECHOE | ISIG);
//no output process , raw set_input
options.c_oflag &=~OPOST;
//min character
options.c_cc[VMIN] = 1;
//time to wait for data
options.c_cc[VTIME] = 15;
tcflush(fd,TCIOFLUSH);
status=tcsetattr(fd, TCSANOW, &options);
if (status != 0)
{
perror("tcsetattr etc abnormal");
exit(-1);
}
}
void serial_config(int fd,int baudrate,serial_format format)
{
serial_speed_set(fd,baudrate);
serial_format_set(fd,format);
serial_etc_config(fd);
}
int main(int argc, char **argv)
{
fd_set set_input;
int fd;
char buff[50]={'\0',};
struct timeval timeout;
int ret_select=0;
int bytes=0;
int write_num=0;
int read_num=0;
int wait=0;
fd = serial_open(0);
serial_config(fd,B9600,SERIAL_8N1);
FD_ZERO(&set_input);
FD_SET(fd,&set_input);
int loop_test=5;
while (loop_test)
{
printf("Loop %d\n",loop_test);
loop_test--;
//tcflush(fd,TCIOFLUSH);
if(loop_test%2)
{
write_num=write(fd,"hello \nlinux world !\n",strlen("hello \nlinux world !\n"));
}
else
{
write_num=write(fd,"hello \nserial port !\n",strlen("hello \nserial port !\n"));
}
printf("now %d bytes has been written to the serial port\n",write_num);
timeout.tv_sec =1;
timeout.tv_usec =0;
ret_select= select(fd+1,&set_input, NULL,NULL,&timeout);
/* See if there was an error */
if (ret_select<0)
perror("select failed");
else if (ret_select == 0)
printf("timeout\n");
else
{
/* We have set_input */
if (FD_ISSET(fd,&set_input))
{
bytes=0;
wait=0;
do
{
ioctl(fd, FIONREAD, &bytes);
}
while( wait++,bytes!=write_num);
printf("now %d bytes is availble to read\n",bytes);
printf("delay %d times for read\n",wait);
read_num = read(fd, buff, bytes);
printf("now %d bytes has been read from serial\n",read_num);
buff[bytes] = '\0';
printf( "%s", buff);
}
}
printf("************\n");
}
close(fd);
exit (0);
}
实际测试截图,截取自linux版slickedit的output窗口
串口接法图【使用跳线帽】
文章评论(0条评论)
登录后参与讨论