Linux串口UART编程--C语言

串口通讯算是最常用的一个通讯方式,此文就对串口编程做一学习和记录,以备今后查阅

常见接口类型

                DB9
针号      功能              缩写
 1      数据载波检测         DCD
 2      接收数据             RXD
 3      发送数据            TXD
 4      数据端准备          DTR
 5      信号地              GND
 6      数据设备准备好       DSR
 7      请求发送            RTS
 8      清除发送            CTS
 9      振铃提示            DELL

其中RXD和TXD与GND比较常用,DTR和RTS这种在加有流控的串口设备上回使用

1.open串口

fd = open("/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
open的参数可以参考io里面的介绍
O_NOCTTY:表示程序不会成为这个端口上的“控制终端”.如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程
O_NDELAY:表示程序并不关心DCD信号线的状态——也就是不关心端口另一端是否已经连接

2.设置串口

设置串口属性包括基本的波特率,校验位,停止位等等
其中最重要的一个结构体 struct termios ,包含了串口相关的全部属性,我们就是设置它来为后边的通讯做准备

#include 
struct termios{
    tcflag_t  c_iflag;  //输入模式标志
    tcflag_t  c_oflag;  //输出模式标志
    tcflag_t  c_cflag;  //控制选项
    tcflag_t  c_lflag;  //行选项
    cc_t      c_cc[NCCS]; //控制字符
};

每个选项都是16位数,每一位都有其含义,接下来就看看这些选项的键值

(1).c_iflag:输入模式标志,控制终端输入方式

    键 值             说 明
    IGNBRK          忽略BREAK键输入
    BRKINT          如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断
    IGNPAR          忽略奇偶校验错误
    PARMRK          标识奇偶校验错误
    INPCK           允许输入奇偶校验
    ISTRIP          去除字符的第8个比特
    INLCR           将输入的NL(换行)转换成CR(回车)
    IGNCR           忽略输入的回车
    ICRNL           将输入的回车转化成换行(如果IGNCR未设置的情况下)
    IUCLC           将输入的大写字符转换成小写字符(非POSIX)
    IXON            允许输入时对XON/XOFF流进行控制
    IXANY           输入任何字符将重启停止的输出
    IXOFF           允许输入时对XON/XOFF流进行控制
    IMAXBEL         当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置

(2).c_oflag:输出模式标志,控制终端输出方式

    键 值             说 明
    OPOST           处理后输出
    OLCUC           将输入的小写字符转换成大写字符(非POSIX)
    ONLCR           将输入的NL(换行)转换成CR(回车)及NL(换行)
    OCRNL           将输入的CR(回车)转换成NL(换行)
    ONOCR           第一行不输出回车符
    ONLRET          不输出回车符
    OFILL           发送填充字符以延迟终端输出
    OFDEL           以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL('\0')(非POSIX)
    NLDLY           换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
    CRDLY           回车延迟,取值范围为:CR0、CR1、CR2和 CR3
    TABDLY          水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
    BSDLY           空格输出延迟,可以取BS0或BS1
    VTDLY           垂直制表符输出延迟,可以取VT0或VT1
    FFDLY           换页延迟,可以取FF0或FF1

(3).c_cflag:控制模式标志,指定终端硬件控制信息

    键 值             说 明
    CBAUD           波特率(4+1位)(非POSIX)
    CBAUDEX         附加波特率(1位)(非POSIX)
    CSIZE           字符长度,取值范围为CS5、CS6、CS7或CS8
    CSTOPB          设置两个停止位
    CREAD           使用接收器
    PARENB          使用奇偶校验
    PARODD          对输入使用奇偶校验,对输出使用偶校验
    HUPCL           关闭设备时挂起
    CLOCAL          忽略调制解调器线路状态
    CRTSCTS         使用RTS/CTS流控制

(4).c_lflag:本地模式标志,控制终端编辑功能

    键 值             说 明
    ISIG            当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
    ICANON          使用标准输入模式
    XCASE           在ICANON和XCASE同时设置的情况下,终端只使用大写。如果只设置了XCASE,则输入字符将被转换为小写字符,除非字符使用了转义字符(非POSIX,且Linux不支持该参数)
    ECHO            显示输入字符
    ECHOE           如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词
    ECHOK           如果ICANON同时设置,KILL将删除当前行
    ECHONL          如果ICANON同时设置,即使ECHO没有设置依然显示换行符
    ECHOPRT         如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
    TOSTOP          向后台输出发送SIGTTOU信号

(5).c_cc[NCCS]:控制字符,用于保存终端驱动程序中的特殊字符

只有在本地模式标志c_lflag中设置了IEXITEN时,POSIX没有定义的控制字符才能在Linux中使用。每个控制字符都对应一个按键组合(^C、^H等)。
VMIN和VTIME这两个控制字符除外,它们不对应控制符。这两个控制字符只在原始模式下才有效。

    键 值             说 明
    c_cc[VMIN]      原始模式(非标准模式)读的最小字符数
    c_cc[VTIME]     原始模式(非标准模式)读时的延时,以十分之一秒为单位

    c_cc[VINTR]     默认对应的控制符是^C,作用是清空输入和输出队列的数据并且向tty设备的前台进程组中的每一个程序发送一个SIGINT信号,对SIGINT信号没有定义处理程序的进程会马上退出。
    c_cc[VQUIT]     默认对应的控制符是^/,作用是清空输入和输出队列的数据并向tty设备的前台进程组中的每一个程序发送一个SIGQUIT信号,对SIGQUIT 信号没有定义处理程序的进程会马上退出。
    c_cc[verase]    默认对应的控制符是^H或^?,作用是在标准模式下,删除本行前一个字符,该字符在原始模式下没有作用。
    c_cc[VKILL]     默认对应的控制符是^U,在标准模式下,删除整行字符,该字符在原始模式下没有作用。
    c_cc[VEOF]      默认对应的控制符是^D,在标准模式下,使用read()返回0,标志一个文件结束。
    c_cc[VSTOP]     默认对应的控制字符是^S,作用是使用tty设备暂停输出直到接收到VSTART控制字符。或者,如果设备了IXANY,则等收到任何字符就开始输出。
    c_cc[VSTART]    默认对应的控制字符是^Q,作用是重新开始被暂停的tty设备的输出。
    c_cc[VSUSP]     默认对应的控制字符是^Z,使当前的前台进程接收到一个SIGTSTP信号。
    c_cc[VEOL]
    c_cc[VEOL2]     在标准模式下,这两个下标在行的末尾加上一个换行符('/n'),标志一个行的结束,从而使用缓冲区中的数据被发送,并开始新的一行。POSIX中没有定义VEOL2。
    c_cc[VREPRINT]  默认对应的控制符是^R,在标准模式下,如果设置了本地模式标志ECHO,使用VERPRINT对应的控制符和换行符在本地显示,并且重新打印当前缓冲区中的字符。POSIX中没有定义VERPRINT。
    c_cc[VWERASE]   默认对应的控制字符是^W,在标准模式下,删除缓冲区末端的所有空格符,然后删除与之相邻的非空格符,从而起到在一行中删除前一个单词的效果。 POSIX中没有定义VWERASE。
    c_cc[VLNEXT]    默认对应的控制符是^V,作用是让下一个字符原封不动地进入缓冲区。如果要让^V字符进入缓冲区,需要按两下^V。POSIX中没有定义 VLNEXT。

3.设置操作函数

以下的一些函数用来设置打开的串口

1.获取属性

int tcgetattr(int fd, struct termios *termios_p);

一般的在设置属性之前先读出原来的串口信息保存前,因为有些参数我们不用修改使用原值就行

2.设置属性

int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);

功能:设置与终端相关的参数 (除非需要底层支持却无法满足),使用termios_p 引用的termios 结构
optional_actions指定了什么时候改变会起作用:       
    TCSANOW:改变立即发生  
    TCSADRAIN:改变在所有写入fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用(当前输出完成时将值改变)  
    TCSAFLUSH :改变在所有写入fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)

3.送break字符

int tcsendbreak(int fd, int duration);
功能:传送连续的0 值比特流,持续一段时间,如果终端使用异步串行数据传输的话。
如果duration 是0,它至少传输 0.25 秒,不会超过 0.5 秒。如果 duration非零,它发送的时间长度由实现定义

4.等待所有输出都被传输

int tcdrain(int fd);

5.刷新IO

int tcflush(int fd, int queue_selector);
功能:丢弃要写入引用的对象,对象由queue_selector 选择:
TCIFLUSH:刷新收到的数据但是不读 
TCOFLUSH:刷新写入的数据但是不传送 
TCIOFLUSH:同时刷新收到的数据但是不读,并且刷新写入的数据但是不传送

6.挂起

int tcflow(int fd, int action);
功能:挂起 fd 引用的对象上的数据传输或接收,取决于action 的值:
    TCOOFF:挂起输出  
    TCOON :重新开始被挂起的输出  
    TCIOFF :发送一个STOP 字符,停止终端设备向系统传送数据  
    TCION :发送一个START 字符,使终端设备向系统传输数据  
打开一个终端设备时的默认设置是输入和输出都没有挂起

7.获取输入速度

speed_t cfgetispeed(const struct termios *termios_p);

8.获取输出速度

speed_t cfgetospeed(const struct termios *termios_p);

9.设置输入速度

int cfsetispeed(struct termios *termios_p, speed_t speed);

10.设置输出速度

int cfsetospeed(struct termios *termios_p, speed_t speed);

11.设置输入输出速度

int cfsetspeed(struct termios *termios_p, speed_t speed);

被用来获取和设置termios 结构中,输入和输出波特率的值。新值不会马上生效,直到成功调用了tcsetattr() 函数。
设置速度为 B0 使得 modem"挂机" 与 B38400 相应的实际比特率可以用setserial(8) 调整。 输入和输出波特率被保存于 termios 结构中
设置termios_p 指向的termios 结构中存储的输出波特率为speed。取值必须是以下常量之一: 
B0       B50       B75       B110       B134        B150       B200       B300       B600       B1200       B1800       
B2400       B4800       B9600       B19200        B38400       B57600       B115200        B230400

4.数据收发

使用文件IO的读写操作来完成
read();
write();

5.close串口

close(fd);

下边是一个测试程序

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int set_opt(int,int,int,char,int);
int uart_send(int fd,void *buf, int len);
int uart_recv_timeout(void *buf, int len, int timeout_ms);
void main()
{
    int fd,ret,i=10;
    char *uart3 = "/dev/ttySAC3";
    char *buffer = "hello world!\n";

    printf("\r\nitop4412 uart3 writetest start\r\n");

    if((fd = open(uart3, O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
    {
        printf("open %s is failed",uart3);
    }
    else
    {
        printf("open %s is success\n",uart3);
        set_opt(fd, 115200, 8, 'N', 1); 
        while(i--)
        {
            ret = uart_send(fd,buffer, strlen(buffer));
            if(ret < 0)
                printf("write failed\n");
            else
            {
                printf("wr_static is %d\n",ret);
            }
            sleep(1);
        }
    }
    close(fd);
}


int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
    struct termios newtio,oldtio;
    /*获取原有串口配置*/
    if  ( tcgetattr( fd,&oldtio)  !=  0) { 
        perror("SetupSerial 1");
        return -1;
    }
    memset( &newtio, 0, sizeof(newtio) );
    /*CREAD 开启串行数据接收,CLOCAL并打开本地连接模式*/
    newtio.c_cflag  |=  CLOCAL | CREAD;

    /*设置数据位*/
    newtio.c_cflag &= ~CSIZE;
    switch( nBits )
    {
    case 7:
        newtio.c_cflag |= CS7;
        break;
    case 8:
        newtio.c_cflag |= CS8;
        break;
    }
    /* 设置奇偶校验位 */
    switch( nEvent )
    {
    case 'O':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
    case 'E': 
        newtio.c_iflag |= (INPCK | ISTRIP);
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
        break;
    case 'N':  
        newtio.c_cflag &= ~PARENB;
        break;
    }
    /* 设置波特率 */
    switch( nSpeed )
    {
    case 2400:
        cfsetispeed(&newtio, B2400);
        cfsetospeed(&newtio, B2400);
        break;
    case 4800:
        cfsetispeed(&newtio, B4800);
        cfsetospeed(&newtio, B4800);
        break;
    case 9600:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
        break;
    case 115200:
        cfsetispeed(&newtio, B115200);
        cfsetospeed(&newtio, B115200);
        break;
    case 460800:
        cfsetispeed(&newtio, B460800);
        cfsetospeed(&newtio, B460800);
        break;
    default:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
        break;
    }
    /*设置停止位*/
    if( nStop == 1 )/*设置停止位;若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOPB*/
        newtio.c_cflag &=  ~CSTOPB;/*默认为一位停止位; */
    else if ( nStop == 2 )
        newtio.c_cflag |=  CSTOPB;
    /*设置最少字符和等待时间,对于接收字符和等待时间没有特别的要求时*/
    newtio.c_cc[VTIME]  = 0;/*非规范模式读取时的超时时间;*/
    newtio.c_cc[VMIN] = 0;/*非规范模式读取时的最小字符数*/
    /*tcflush清空终端未完成的输入/输出请求及数据;TCIFLUSH表示清空正收到的数据,且不读取出来 */
    tcflush(fd,TCIFLUSH);
    if((tcsetattr(fd,TCSANOW,&newtio))!=0)
    {
        perror("com set error");
        return -1;
    }
//  printf("set done!\n\r");
    return 0;
}

int uart_send(int fd,void *buf, int len)
{
    int ret = 0;
    int count = 0;

    tcflush(fd, TCIFLUSH);

    while (len > 0) 
    {

        ret = write(fd, (char*)buf + count, len);
        if (ret < 1) 
        {
            break;
        }
        count += ret;
        len = len - ret;
    }

    return count;
}

int uart_recv_timeout(void *buf, int len, int timeout_ms)
{
    int ret;
    size_t  rsum = 0;
    ret = 0;
    fd_set rset;
    struct timeval t;

    while (rsum < len)
    {
        t.tv_sec = timeout_ms/1000;
        t.tv_usec = (timeout_ms - t.tv_sec*1000)*1000;
        FD_ZERO(&rset);
        FD_SET(uart_fd, &rset);
        ret = select(uart_fd+1, &rset, NULL, NULL, timeout);
        if (ret <= 0) {
            if (ret == 0) 
            {
                //timeout
                return -1;
            }
            if (errno == EINTR) 
            {
                // 信号中断
                continue;
            }
            return -errno;
        } 
        else
        {
            ret = read(uart_fd, (char *)buf + rsum, len - rsum);
            if (ret < 0)
            {
                return ret;
            }
            else
            {
                rsum += ret;
            }
        }
    }

    return rsum;
}

你可能感兴趣的:(Linux)