你好!这里是风筝的博客,
欢迎和我一起多多交流。
之前文章讲过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
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
char val[] = "hello world
";
fd = open("/dev/ttySAC1", O_RDWR);
if (fd < 0)
printf("can't open dev
");
else
printf("can open dev
");
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
#include
#include
#include
#include
#include
#include
#include
#include
struct termios opt;
fd_set uart_read;
struct timeval time;
int len,read_flag;
char rcv_buf[100];
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
";
pid_t fpid;
fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY);
if (fd < 0)
printf("can't open dev
");
else
printf("can open dev
");
if(0 == isatty(STDIN_FILENO))
printf("standard input is not a terminal device/n");
set_uart(fd,115200,0,8,1,'N');
FD_ZERO(&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)
{
while(1)
{
read_flag = select(fd+1,&uart_read,NULL,NULL,NULL);
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
val = %s
",len,rcv_buf);
}
else
{
printf(" receive data error !
");
}
usleep(10000);
}
}
write(fd, val, strlen(val));
while(1)
{
}
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};
if (tcgetattr(fd, &opt) != 0)
{
printf("fail get fd data
");
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
");
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
");
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
");
return -1;
}
switch (stopbits)
{
case 1:
opt.c_cflag &= ~CSTOPB;
break;
case 2:
opt.c_cflag |= CSTOPB;
break;
default:
printf("Unsupported stop bits
");
return -1;
}
opt.c_oflag &= ~OPOST;
opt.c_oflag &= ~(ONLCR | OCRNL);
opt.c_iflag &= ~(ICRNL | INLCR);
opt.c_iflag &= ~(IXON | IXOFF | IXANY);
tcflush(fd, TCIFLUSH);
opt.c_cc[VTIME] = 0;
opt.c_cc[VMIN] = 0;
if (tcsetattr(fd,TCSANOW,&opt) != 0)
{
printf("uart set error!
");
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.
");
wait_flag = 0;
}
int main(int argc, char **argv)
{
int fd;
char val[] = "hello world
";
pid_t fpid;
fd = open("/dev/ttySAC1", O_RDWR | O_NOCTTY);
if (fd < 0)
printf("can't open dev
");
else
printf("can open dev
");
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);
fcntl (fd, F_SETOWN, getpid ());
fcntl (fd, F_SETFL, FASYNC);
while(1)
{
usleep(100000);
if(wait_flag == 0)
{
memset(rcv_buf,0,15);
res = read(fd,rcv_buf,15);
printf("nread=%d,%s
",res,rcv_buf);
wait_flag = 1;
}
}
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(‘