一、串口设备节点

Linux串口应用编程详解(Serial)_ios

二、访问串口

1. 打开串口

#include <stdio.h>   /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */

/*
* 'open_port()' - Open serial port 1.
*
* Returns the file descriptor on success or -1 on error.
*/

int open_port(void)
{
int fd; /* File descriptor for the port */

fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
/*
* Could not open the port.
*/
perror("open_port: Unable to open /dev/ttyf1 - ");
} else {
fcntl(fd, F_SETFL, 0);
}

return (fd);
}

额外的设置标志:

  • ​O_NOCTTY ​​:告诉内核这个程序不想作为控制终端。如果不指定该标志,任何输入都会影响程序(比如键盘中止信号等)。
  • ​O_NDELAY ​​:告诉内核这个程序不关注DCD信号线的状态。如果不指定该标志,当DCD信号线是空电压值的时候,程序将会进入睡眠。

2. 发送数据

n = write(fd, "ATZ\r\n", 5);
if (n < 0) {
fputs("write() of 5 bytes failed!\n", stderr);
}

write函数写完后会返回发送的字节数,如果发生错误会返回-1.

3. 接收数据

(1)在原始数据模式操作串口时,使用read函数即可。

成功读取时,返回串口输入buffer中实际可用的字符数量,当串口输入buffer中没有可用字符时,会引发堵塞直到新的字符到来。

read函数也可以设置直接返回,当读取不到可用字符时,立即返回0:

fcntl(fd, F_SETFL, FNDELAY);

如果想要恢复read函数的堵塞机制,可以再次设置:

fcntl(fd, F_SETFL, 0);

4. 关闭串口

close(fd);

三、设置串口(重点)

1. POSIX Terminal 接口

大多数系统都支持POSIX terminal(serial) 接口来改变串口参数,比如波特率,数据长度等。

首先包含头文件:

#include <termios.h>

该文件中和 POSIX 控制函数一样,定义了终端控制结构体Termios:

typedef unsigned char cc_t;
typedef unsigned int speed_t;
typedef unsigned int tcflag_t;

#define NCCS 32
struct termios
{
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};

其中每项成员的意义如下:

成员

意义

c_cflag

控制设置

c_lflag

本地设置

c_iflag

输入设置

c_oflag

输出设置

c_cc

控制字符

c_ispeed

输入速率

c_ospeed

输出速率

两个最重要的 POSIX 函数是 ​​tcgetattr​​​ 和 ​​tcsetattr​​,获取参数和设置参数,原型如下:

/* Put the state of FD into *TERMIOS_P.  */
extern int tcgetattr (int __fd, struct termios *__termios_p) __THROW;

/* Set the state of FD to *TERMIOS_P.
Values for OPTIONAL_ACTIONS (TCSA*) are in <bits/termios.h>. */
extern int tcsetattr (int __fd, int __optional_actions,
const struct termios *__termios_p) __THROW;

tcsetattr 在设置参数时可选择的标志如下:

/* tcsetattr uses these */
#define TCSANOW 0
#define TCSADRAIN 1
#define TCSAFLUSH 2
  • TCSANOW:立即设置,不用等数据完成
  • TCSADRAIN:等到所有数据都发送完成再设置
  • TCSAFLUSH:刷新输入和输出buffer并且完成设置

2. 控制设置(Control Options)

c_cflag成员控制波特率,数据位、校验位、停止位的宽度,和硬件流控。

2.1. 可选常量

这些设置选项支持的值都已经被定义为常量,如下。

(1)波特率

#define  B0 0000000   /* hang up */
#define B50 0000001
#define B75 0000002
#define B110 0000003
#define B134 0000004
#define B150 0000005
#define B200 0000006
#define B300 0000007
#define B600 0000010
#define B1200 0000011
#define B1800 0000012
#define B2400 0000013
#define B4800 0000014
#define B9600 0000015
#define B19200 0000016
#define B38400 0000017

#define B57600 0010001
#define B115200 0010002
#define B230400 0010003
#define B460800 0010004
#define B500000 0010005
#define B576000 0010006
#define B921600 0010007
#define B1000000 0010010
#define B1152000 0010011
#define B1500000 0010012
#define B2000000 0010013
#define B2500000 0010014
#define B3000000 0010015
#define B3500000 0010016
#define B4000000 0010017
#define __MAX_BAUD B4000000

(2)数据位

#define   CS5 0000000
#define CS6 0000020
#define CS7 0000040
#define CS8 0000060

(3)停止位

//设置该值则为2个停止位,不设置则为1个停止位
#define CSTOPB 0000100

(4)校验位

//使能校验位
#define PARENB 0000400
//设置该值则使用奇校验,否则使用偶校验
#define PARODD 0001000

2.2. 建议使能的标志

还有两个选项应该一直被启用:

#define CREAD 0000200
#define CLOCAL 0004000

这两个设置确保你的程序不会变为端口的所有者而受到影响,并且串口驱动程序将会速度传入的数据。

在设置 c_cflag 成员的时候,一定不能直接赋值,要使用位操作来设置或者清除对应的位。

2.3. 设置示例

(1)设置波特率为115200

struct termios options;

/*
* Get the current options for the port...
*/

tcgetattr(fd, &options);

/*
* Set the baud rates to 115200...
*/

cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);

/*
* Enable the receiver and set local mode...
*/

options.c_cflag |= (CLOCAL | CREAD);

/*
* Set the new options for the port...
*/

tcsetattr(fd, TCSANOW, &options)

(2)设置数据位为8位

options.c_cflag &= ~CSIZE; /* Mask the character size bits */
options.c_cflag |= CS8; /* Select 8 data bits */

(3)设置无校验位

options.c_cflag &= ~PARENB
options.c_cflag &= ~CSTOPB
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

(6)设置无硬件流控

options.c_cflag &= ~CNEW_RTSCTS;

3. 本地设置(Local Options)

c_lflag 成员控制输入的字符如何被串口驱动管理,一般用来设置是否使用原始数据模式即可。

开启原始数据模式:

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

四、串口回传示例

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <termios.h>
#include <string.h>

/* 115200, 8, N, 1 */
int uart_setup(int fd)
{
struct termios options;

// 获取原有串口配置
if (tcgetattr(fd, &options) < 0) {
return -1;
}

// 修改控制模式,保证程序不会占用串口
options.c_cflag |= CLOCAL;

// 修改控制模式,能够从串口读取数据
options.c_cflag |= CREAD;

// 不使用流控制
options.c_cflag &= ~CRTSCTS;

// 设置数据位
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;

// 设置奇偶校验位
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;

// 设置停止位
options.c_cflag &= ~CSTOPB;

// 设置最少字符和等待时间
options.c_cc[VMIN] = 1; // 读数据的最小字节数
options.c_cc[VTIME] = 0; //等待第1个数据,单位是10s

// 修改输出模式,原始数据输出
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

// 设置波特率
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);

// 清空终端未完成的数据
tcflush(fd, TCIFLUSH);

// 设置新属性
if(tcsetattr(fd, TCSANOW, &options) < 0) {
return -1;
}

return 0;
}

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

if (argc != 2) {
printf("usage: ./test_uart [device]\n");
return -1;
}

/* 打开串口 */
fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
printf("open dev fail!\n");
return -1;
} else {
fcntl(fd, F_SETFL, 0);
}

/* 设置串口 */
ret = uart_setup(fd);
if (ret < 0) {
printf("uart setup fail!\n");
close(fd);
return -1;
}

/* 串口回传实验 */
while (1) {
scanf("%c", &ch);
ret = write(fd, &ch, 1);
printf("write [%c] , ret is %d!\r\n", ch, ret);

ret = read(fd, &ch, 1);
if (ret < 1) {
printf("read fail, ret is %d\r\n", ret);
} else {
printf("recv a char:[0x%02x][%c]\r\n", ch, ch);
}
}

close(fd);
}

编译:

arm-linux-gnueabihf-gcc uart_app.c -o uart_app

在开发板上执行:

Linux串口应用编程详解(Serial)_#define_02