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

一、串口设备节点

二、访问串口

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 函数是 tcgetattrtcsetattr,获取参数和设置参数,原型如下:

/* 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

在开发板上执行:

猜你喜欢

转载自blog.csdn.net/Mculover666/article/details/124174276
今日推荐