Linux 串口编程<四> 串口设备程序开发
2017-05-19 17:59
357 查看
Linux 串口编程和程序相对来说是很简单的,之所以用博客连载来展示,主要是想在学会使用的基础上掌握相关背景,原理以及注意事项。相信在遇到问题的时候,我们就不会对于技术的概念和 API 的使用浅尝辄止了。下面进入具体应用案例,由于现在很多电脑已经没有引出串口以及波特率范围会受到限制,这里我以 CH340 USB 转串口芯片制作的模块为基础讲解串口应用程序开发,关于该芯片在 Linux 系统的使用以及驱动加载可以参考:CH340 Linux驱动使用教程。
函数功能:根据传入的串口设备名打开相应的设备。成功返回设备句柄,失败返回-1。
2. int libtty_close(int fd);
函数功能:关闭打开的设备句柄。成功返回0,失败返回负值。
函数功能:配置串口设备,传入参数依次为波特率设置、数据位设置、停止位设置、检验设置。
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 设备,那么也返回出错;
使用 CLOCAL 用于忽略所有 MODEM 状态信号线,CREAD 标志用于使能接收。CSIZE 为数据位掩码;
调用 cfsetispeed与cfsetospeed参数设置波特率,函数中引用了两个数组,在后面的完整代码中会看到,这样书写可以简化设置代码;
通过控制 c_cflag 与 c_iflag 配置串口数据位、停止位以及校验设置等;
VTIME与VMIN作用已经讲解多次,不再赘述,值得注意的是,TIME 值的单位是十分之一秒;
通过 tcflush清空输入和输出缓冲区,根据实际需要修改;
最后通过 tcsetattr 函数对将配置实际作用于串口;
数据读写直接使用 read、write 函数接口就可以了,因此没有列举出来。下面给出完整的串口读写测试代码:
关于 Linux 串口编程的其他文章,可以移步至以下链接:《Linux 串口编程<一> 一些背景》
《Linux 串口编程<二> 深入了解 termios》
《Linux 串口编程<三> 使用termios与API 进行串口程序开发》
《Linux 串口编程<四> 串口设备程序开发》
有疑问的读者可以给我邮件或者评论,觉得对你有帮助就点赞吧~:-D
设备的打开与关闭
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 为数据位掩码;
调用 cfsetispeed与cfsetospeed参数设置波特率,函数中引用了两个数组,在后面的完整代码中会看到,这样书写可以简化设置代码;
通过控制 c_cflag 与 c_iflag 配置串口数据位、停止位以及校验设置等;
VTIME与VMIN作用已经讲解多次,不再赘述,值得注意的是,TIME 值的单位是十分之一秒;
通过 tcflush清空输入和输出缓冲区,根据实际需要修改;
最后通过 tcsetattr 函数对将配置实际作用于串口;
数据读写直接使用 read、write 函数接口就可以了,因此没有列举出来。下面给出完整的串口读写测试代码:
/* 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
相关文章推荐
- Linux 串口编程<三> 使用termios与API进行串口程序开发
- Linux下串口程序开发 转帖
- Linux下串口程序开发
- Linux设备模型<四>实例一winter_kobject
- <2012 11 6 > linux设备驱动程序开发初探(6) 异步通讯机制: Kernel ---> APP
- 【Qt开发】【Linux开发】Qt程序在嵌入式设备(arm) 上运行,鼠标擦除界面的解决方案
- Linux_Hi3518E开发_SPI Flash程序下载成功后串口打印信息
- 【嵌入式开发技术之串口】Linux下串口主机程序
- <2012 11 6 > linux设备驱动程序开发初探(4) linux的中断体系_按键中断驱动程序编写
- Linux之设备驱动学习简过程<四>
- Linux下串口程序开发
- 转载只为记录经典之<linux下简单的设备驱动开发 >
- <2012 11 3 > linux设备驱动程序开发初探(2) udev/mdev机制 LED的驱动程序
- <2012 11 4 > linux设备驱动程序开发初探(3) 练习:从零写一个查询式按键驱动程序
- Linux+树莓派3开发总结——根据设备ID等信息固定串口号
- linux设备上的Onvif 实现3 :gSOAP嵌入式linux下的移植与程序开发
- <2012 11 3 > linux设备驱动程序开发初探(1) 目次 概念 框架 最小驱动程序
- Linux下串口程序开发
- Libusb开发教程<三> USB设备程序开发
- 【嵌入式开发技术之串口】Linux下串口主机程序