您的位置:首页 > 运维架构 > Linux

Linux 串口编程<四> 串口设备程序开发

2017-05-19 17:59 357 查看
Linux 串口编程和程序相对来说是很简单的,之所以用博客连载来展示,主要是想在学会使用的基础上掌握相关背景,原理以及注意事项。相信在遇到问题的时候,我们就不会对于技术的概念和 API 的使用浅尝辄止了。下面进入具体应用案例,由于现在很多电脑已经没有引出串口以及波特率范围会受到限制,这里我以 CH340 USB 转串口芯片制作的模块为基础讲解串口应用程序开发,关于该芯片在 Linux 系统的使用以及驱动加载可以参考:CH340 Linux驱动使用教程

设备的打开与关闭

1. int libtty_open(const char *devname);
函数功能:根据传入的串口设备名打开相应的设备。成功返回设备句柄,失败返回-1。

2. int libtty_close(int fd);
函数功能:关闭打开的设备句柄。成功返回0,失败返回负值。

设备的配置

1. int libtty_setopt(int fd, int speed, char databits, char stopbits, char parity);
函数功能:配置串口设备,传入参数依次为波特率设置、数据位设置、停止位设置、检验设置。
Notes:设备打开前,可以通过 ls /dev 确认自己的硬件设备名,对于 USB 转串口 IC,在系统下名称为 "ttyUSBx",设备序号是根据插入主机的先后顺序自动分配的,这里我的为 "ttyUSB0",读者根据自己的需要修改。
/**
* libtty_open - open tty device
* @devname: the device name to open
*
* In this demo device is opened blocked, you could modify it at will.
*/
int libtty_open(const char *devname)
{
int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY);
int flags = 0;

if (fd == -1) {
perror("open device failed");
return -1;
}

flags = fcntl(fd, F_GETFL, 0);
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
printf("fcntl failed.\n");
return -1;
}

if (isatty(fd) == 0)
{
printf("not tty device.\n");
return -1;
}
else
printf("tty device test ok.\n");

return fd;
}
Note:传入的 devname 参数为设备绝对路径;

O_NOCTTY标志用于通知系统,这个程序不会成为对应这个设备的控制终端。如果没有指定这个标志,那么任何一个输入(如SIGINT等)都将会影响用户的进程;
O_NDELAY标志与O_NONBLOCK 等效,但这里不仅仅是设置为非阻塞,还用于通知系统,这个程序不关心 DCD 信号线所处的状态(即与设备相连的另一端是否激活或者停止)。如果用户指定了这一标志,则进程将会一直处在休眠状态,直到 DCD 信号线被激活;
使用 fcntl 函数恢复设备状态为阻塞状态,在数据收发时就会进行等待;
使用 isatty函数测试当前打开的设备句柄是否关联到终端设备,如果不是 tty 设备,那么也返回出错;
/**
* libtty_setopt - config tty device
* @fd: device handle
* @speed: baud rate to set
* @databits: data bits to set
* @stopbits: stop bits to set
* @parity: parity set
*
* The function return 0 if success, or -1 if fail.
*/
int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity)
{
struct termios newtio;
struct termios oldtio;
int i;

bzero(&newtio, sizeof(newtio));
bzero(&oldtio, sizeof(oldtio));

if (tcgetattr(fd, &oldtio) != 0) {
perror("tcgetattr");
return -1;
}
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;

/* set tty speed */
for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if (speed == name_arr[i]) {
cfsetispeed(&newtio, speed_arr[i]);
cfsetospeed(&newtio, speed_arr[i]);
}
}

/* set data bits */
switch (databits) {
case 5:
newtio.c_cflag |= CS5;
break;
case 6:
newtio.c_cflag |= CS6;
break;
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
default:
fprintf(stderr, "unsupported data size\n");
return -1;
}

/* set parity */
switch (parity) {
case 'n':
case 'N':
newtio.c_cflag &= ~PARENB;    /* Clear parity enable */
newtio.c_iflag &= ~INPCK;     /* Disable input parity check */
break;
case 'o':
case 'O':
newtio.c_cflag |= (PARODD | PARENB); /* Odd parity instead of even */
newtio.c_iflag |= INPCK;     /* Enable input parity check */
break;
case 'e':
case 'E':
newtio.c_cflag |= PARENB;    /* Enable parity */
newtio.c_cflag &= ~PARODD;   /* Even parity instead of odd */
newtio.c_iflag |= INPCK;     /* Enable input parity check */
break;
default:
fprintf(stderr, "unsupported parity\n");
return -1;
}

/* set stop bits */
switch (stopbits) {
case 1:
newtio.c_cflag &= ~CSTOPB;
break;
case 2:
newtio.c_cflag |= CSTOPB;
break;
default:
perror("unsupported stop bits\n");
return -1;
}

newtio.c_cc[VTIME] = 0;	  /* Time-out value (tenths of a second) [!ICANON]. */
newtio.c_cc[VMIN] = 0;    /* Minimum number of bytes read at once [!ICANON]. */

tcflush(fd, TCIOFLUSH);

if (tcsetattr(fd, TCSANOW, &newtio) != 0)
{
perror("tcsetattr");
return -1;
}
return 0;
}
Note:首先保存了原先串口配置,为了方便演示,这里保存为局部变量,实际使用时是需要把配置保存到全局 termios 结构体中的。使用tcgetattr还可以测试配置是否正确、串口是否可用等。返回值参见 man 手册;
使用 CLOCAL 用于忽略所有 MODEM 状态信号线,CREAD 标志用于使能接收。CSIZE 为数据位掩码;
调用 cfsetispeedcfsetospeed参数设置波特率,函数中引用了两个数组,在后面的完整代码中会看到,这样书写可以简化设置代码;
通过控制 c_cflag 与 c_iflag 配置串口数据位、停止位以及校验设置等;
VTIMEVMIN作用已经讲解多次,不再赘述,值得注意的是,TIME 值的单位是十分之一秒;
通过 tcflush清空输入和输出缓冲区,根据实际需要修改;
最后通过 tcsetattr 函数对将配置实际作用于串口;
数据读写直接使用 readwrite 函数接口就可以了,因此没有列举出来。下面给出完整的串口读写测试代码:
/* TTY testing utility (using tty driver)
* Copyright (c) 2017
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* Cross-compile with cross-gcc -I /path/to/cross-kernel/include
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

int speed_arr[] = {
B115200,
B57600,
B38400,
B19200,
B9600,
B4800,
B2400,
B1200,
B300
};

int name_arr[] = {
115200,
57600,
38400,
19200,
9600,
4800,
2400,
1200,
300
};

/** * libtty_setopt - config tty device * @fd: device handle * @speed: baud rate to set * @databits: data bits to set * @stopbits: stop bits to set * @parity: parity set * * The function return 0 if success, or -1 if fail. */ int libtty_setopt(int fd, int speed, int databits, int stopbits, char parity) { struct termios newtio; struct termios oldtio; int i; bzero(&newtio, sizeof(newtio)); bzero(&oldtio, sizeof(oldtio)); if (tcgetattr(fd, &oldtio) != 0) { perror("tcgetattr"); return -1; } newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; /* set tty speed */ for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) { if (speed == name_arr[i]) { cfsetispeed(&newtio, speed_arr[i]); cfsetospeed(&newtio, speed_arr[i]); } } /* set data bits */ switch (databits) { case 5: newtio.c_cflag |= CS5; break; case 6: newtio.c_cflag |= CS6; break; case 7: newtio.c_cflag |= CS7; break; case 8: newtio.c_cflag |= CS8; break; default: fprintf(stderr, "unsupported data size\n"); return -1; } /* set parity */ switch (parity) { case 'n': case 'N': newtio.c_cflag &= ~PARENB; /* Clear parity enable */ newtio.c_iflag &= ~INPCK; /* Disable input parity check */ break; case 'o': case 'O': newtio.c_cflag |= (PARODD | PARENB); /* Odd parity instead of even */ newtio.c_iflag |= INPCK; /* Enable input parity check */ break; case 'e': case 'E': newtio.c_cflag |= PARENB; /* Enable parity */ newtio.c_cflag &= ~PARODD; /* Even parity instead of odd */ newtio.c_iflag |= INPCK; /* Enable input parity check */ break; default: fprintf(stderr, "unsupported parity\n"); return -1; } /* set stop bits */ switch (stopbits) { case 1: newtio.c_cflag &= ~CSTOPB; break; case 2: newtio.c_cflag |= CSTOPB; break; default: perror("unsupported stop bits\n"); return -1; } newtio.c_cc[VTIME] = 0; /* Time-out value (tenths of a second) [!ICANON]. */ newtio.c_cc[VMIN] = 0; /* Minimum number of bytes read at once [!ICANON]. */ tcflush(fd, TCIOFLUSH); if (tcsetattr(fd, TCSANOW, &newtio) != 0) { perror("tcsetattr"); return -1; } return 0; }

/**
* libtty_open - open tty device
* @devname: the device name to open
*
* In this demo device is opened blocked, you could modify it at will.
*/
int libtty_open(const char *devname)
{
int fd = open(devname, O_RDWR | O_NOCTTY | O_NDELAY);
int flags = 0;

if (fd == -1) {
perror("open device failed");
return -1;
}

flags = fcntl(fd, F_GETFL, 0);
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
printf("fcntl failed.\n");
return -1;
}

if (isatty(fd) == 0)
{
printf("not tty device.\n");
return -1;
}
else
printf("tty device test ok.\n");

return fd;
}

/**
* libtty_close - close tty device
* @fd: the device handle
*
*/
int libtty_close(int fd)
{
return close(fd);
}

void tty_test(int fd)
{
int nwrite, nread;
char buf[100];

memset(buf, 0x32, sizeof(buf));

while (1) {
nwrite = write(fd, buf, sizeof(buf));
printf("wrote %d bytes already.\n", nwrite);
nread = read(fd, buf, sizeof(buf));
printf("read %d bytes already.\n", nread);
sleep(2);
}

}

int main(int argc, char *argv[])
{
int fd;
int ret;

fd = libtty_open("/dev/ttyUSB0");
if (fd < 0) {
printf("libtty_open error.\n");
exit(0);
}

ret = libtty_setopt(fd, 9600, 8, 1, 'n');
if (ret != 0) {
printf("libtty_setopt error.\n");
exit(0);
}

tty_test(fd);

ret = libtty_close(fd);
if (ret != 0) {
printf("libtty_close error.\n");
exit(0);
}
}
执行成功的话,会在终端屏幕上看到每隔两秒输出串口成功发送和接收的字节数,测试时可以直接短接串口的发送和接收引脚进行测试。以下为成功测试截图:



关于 Linux 串口编程的其他文章,可以移步至以下链接:《Linux 串口编程<一> 一些背景》
《Linux 串口编程<二> 深入了解 termios》
《Linux 串口编程<三> 使用termios与API 进行串口程序开发》
《Linux 串口编程<四> 串口设备程序开发》

有疑问的读者可以给我邮件或者评论,觉得对你有帮助就点赞吧~:-D
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: