一、串口设备节点
二、访问串口
1. 打开串口
/* Standard input/output definitions */
/* String function definitions */
/* UNIX standard function definitions */
/* File control definitions */
/* Error number definitions */
/* 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) 接口来改变串口参数,比如波特率,数据长度等。
首先包含头文件:
该文件中和 POSIX 控制函数一样,定义了终端控制结构体Termios:
typedef unsigned char cc_t;
typedef unsigned int speed_t;
typedef unsigned int tcflag_t;
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 */
};
其中每项成员的意义如下:
成员 | 意义 |
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 */
- TCSANOW:立即设置,不用等数据完成
- TCSADRAIN:等到所有数据都发送完成再设置
- TCSAFLUSH:刷新输入和输出buffer并且完成设置
2. 控制设置(Control Options)
c_cflag成员控制波特率,数据位、校验位、停止位的宽度,和硬件流控。
2.1. 可选常量
这些设置选项支持的值都已经被定义为常量,如下。
(1)波特率
/* hang up */
(2)数据位
(3)停止位
//设置该值则为2个停止位,不设置则为1个停止位
(4)校验位
//使能校验位
//设置该值则使用奇校验,否则使用偶校验
2.2. 建议使能的标志
还有两个选项应该一直被启用:
这两个设置确保你的程序不会变为端口的所有者而受到影响,并且串口驱动程序将会速度传入的数据。
在设置 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);
四、串口回传示例
/* 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
在开发板上执行: