Linux下串口编程(C语言版本)

Linux 系统下串口编程

1.准备工具

案例选择在Ubuntu下创建虚拟串口,作为收发使用,需要用到socat命令。
首先进行安装,本人已经安装好了,使用安装命令后,所以下面会提示一些信息,记得连网^ ^

root@lidimini-virtual-machine:/home/lidimini# apt install socat
正在读取软件包列表... 完成
正在分析软件包的依赖关系树       
正在读取状态信息... 完成       
socat 已经是最新版 (1.7.3.2-2ubuntu2)。
升级了 0 个软件包,新安装了 0 个软件包,要卸载 0 个软件包,有 119 个软件包未被升级。
root@lidimini-virtual-machine:/home/lidimini# 

执行下面这句话

root@lidimini-virtual-machine:/home/lidimini# socat -d -d pty,raw,echo=0 pty,raw,echo=0

然后程序会停留,如下图:

root@lidimini-virtual-machine:/home/lidimini# socat -d -d pty,raw,echo=0 pty,raw,echo=0
2021/04/28 11:50:23 socat[6045] N PTY is /dev/pts/1
2021/04/28 11:50:23 socat[6045] N PTY is /dev/pts/2
2021/04/28 11:50:23 socat[6045] N starting data transfer loop with FDs [5,5] and [7,7]

看到/dev/pts/1 和 /dev/pts/2 就是一组互为收发的串口

2.此案例用到的库

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <error.h>
#include <termios.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/stat.h>

3.操作流程

Linux下设备皆文件,要操作串口设备与操作其他文件一样,需要open,write,read函数

3.1 首先打开设备文件

int serport1fd;
serport1fd = open(argv[1],O_RDWR | O_NOCTTY | O_NDELAY);//读写模式,不成为控制终端程序,不受其他程序输出输出影响
//argv[1] 为设备路径
if(serport1fd < 0){
    printf("%s open faild\r\n",argv[1]);
    return -1;//若打开错误则退出返回
}

打开之后可进行读写,但是串口设备不同,要设置串口的基本参数,下面需要用到struct termios这个结构体。

3.2 termios这个结构主要包含的成员有

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
};
  //这个文件在termios.h中

一般串口通信需要对 c_cflag 、c_cc以及c_ispeed和c_ospeed进行设置,即可满足基本的收发需求,下面将对c_cflag进行说明,这个参数要设置的东西有点多。

/* c_cflag bit meaning */
#ifdef __USE_MISC
# define CBAUD	0010017
#endif
#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
#ifdef __USE_MISC
# define EXTA B19200
# define EXTB B38400
#endif
#define CSIZE	0000060 /*数据位屏蔽*/
#define   CS5	0000000 /*5,6,7,8为数据位*/
#define   CS6	0000020
#define   CS7	0000040
#define   CS8	0000060 
#define CSTOPB	0000100 /*停止位*/
#define CREAD	0000200 /*接收标志位*/
#define PARENB	0000400 /*奇偶校验位开启标志位*/
#define PARODD	0001000 /*奇校验,否则偶校验*/
#define HUPCL	0002000
#define CLOCAL	0004000 /*本地连接标志位*/
#ifdef __USE_MISC
# define CBAUDEX 0010000
#endif
#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
#ifdef __USE_MISC
# define CIBAUD	  002003600000		/* input baud rate (not used) */
# define CMSPAR   010000000000		/* mark or space (stick) parity */
# define CRTSCTS  020000000000		/* flow control */
#endif

CSTOPB ——停止位 值为2时,为两位停止,值为1时,为1位停止。
CREAD ——接收标志位, 置位后则开启。
PARENB——奇偶校验位开启标志位, 置位则代表开启
PARODD——奇校验,否则偶校验
CLOCAL——本地连接标志位 ,置位则开启连接
CSIZE——数据位屏蔽 ,置位则开启数据位屏蔽(本人没试过开启)

3.4 进行c_cflag设置

typedef struct termios termios_t;
termios_t *ter_s = malloc(sizeof(*ter_s));
bzero(ter_s,sizeof(*ter_s));//初始化,清零
ter_s->c_cflag |= CLOCAL | CREAD; //激活本地连接与接受使能
ter_s->c_cflag &= ~CSIZE;//失能数据位屏蔽
ter_s->c_cflag |= CS8;//8位数据位
ter_s->c_cflag &= ~CSTOPB;//1位停止位
ter_s->c_cflag &= ~PARENB;//无校验位

下面设置波特率

cfsetispeed(ter_s,B115200);//设置输入波特率
cfsetospeed(ter_s,B115200);//设置输出波特率

调用上面两个函数进行输出波特率设置,输入波特率设置,其实就是对下面这个两个参数进行设置。

speed_t c_ispeed;		/* input speed */
speed_t c_ospeed;		/* output speed */

3.5 进行对c_cc进行设置,c_cc是个数组,数组下标对应的元素含义如下:

/* c_cc characters */
#define VINTR 0
#define VQUIT 1
#define VERASE 2
#define VKILL 3
#define VEOF 4
#define VTIME 5
#define VMIN 6
#define VSWTC 7
#define VSTART 8
#define VSTOP 9
#define VSUSP 10
#define VEOL 11
#define VREPRINT 12
#define VDISCARD 13
#define VWERASE 14
#define VLNEXT 15
#define VEOL2 16

具体的含义还没有查,不过案例满足基本的串口收发,需要设置VTIME,VMIN即可。

ter_s->c_cc[VTIME] = 0;
ter_s->c_cc[VMIN] = 0;
/*1 VMIN> 0 && VTIME> 0
        VMIN为最少读取的字符数,当读取到一个字符后,会启动一个定时器,在定时器超时事前,如果已经读取到了VMIN个字符,则read返回VMIN个字符。如果在接收到VMIN个字符之前,定时器已经超时,则read返回已读取到的字符,注意这个定时器会在每次读取到一个字符后重新启用,即重新开始计时,而且是读取到第一个字节后才启用,也就是说超时的情况下,至少读取到一个字节数据。
        2 VMIN > 0 && VTIME== 0
        在只有读取到VMIN个字符时,read才返回,可能造成read被永久阻塞。
        3 VMIN == 0 && VTIME> 0
        和第一种情况稍有不同,在接收到一个字节时或者定时器超时时,read返回。如果是超时这种情况,read返回值是0。
        4 VMIN == 0 && VTIME== 0
        这种情况下read总是立即就返回,即不会被阻塞。----by 解释粘贴自博客园
    */

设置好上面参数后调用函数

tcflush(serport1fd,TCIFLUSH);//刷清未处理的输入和或输出 
if(tcsetattr(serport1fd,TCSANOW,ter_s) != 0){
            printf("com set error!\r\n");
}
    

tclflush的操作为对串口的文件描述符进行刷新设置,TCIFLUSH意思—— 刷新收到的数据但是不读

4.串口收发

下面的发送函数、接收函数分别用线程来实现。

1.发送函数

typedef struct serial_data{

    char databuf[100];//发送/接受数据
    int serfd;//串口文件描述符

}ser_Data;

void *sersend(void *arg)//串口发送线程函数
{
    ser_Data *snd = (ser_Data *)arg ;
    int ret;
    while(1){
       ret = write(snd->serfd,snd->databuf,strlen(snd->databuf));
       if(ret > 0){
            printf("send success, data is  %s\r\n",snd->databuf);
       }else{
           printf("send error!\r\n");
       }
       usleep(300000);
       /*
       if(发生中断)
       break;//退出
       */
    }
}

2.接收函数

void *serrecv(void *arg)//串口发送线程函数
{
 ser_Data *rec= (ser_Data *)arg ;
    int ret;
    while(1){
       ret = read(rec->serfd,rec->databuf,1024);
       if(ret > 0){
            printf("recv success,recv size is %d,data is  %s\r\n",ret,rec->databuf);
       }else{
           /*
            什么也不做
           */
       }
       usleep(1000);
       /*
       if(发生中断)
       break;//退出
       */
    }
}

收发就是基本的文件读写,直接操作即可。

下面是详细的代码

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <error.h>
#include <termios.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/stat.h>
typedef struct termios termios_t;

typedef struct serial_data{

    char databuf[100];//发送/接受数据
    int serfd;//串口文件描述符

}ser_Data;

void *sersend(void *arg);
void *serrecv(void *arg);

int main(int argc,char *argv[])
{
    pthread_t pid1,pid2;
    pthread_attr_t *pthread_arr1,*pthread_arr2;
    pthread_arr1 = NULL;
    pthread_arr2 = NULL;
    int serport1fd;


    /*   进行串口参数设置  */
    termios_t *ter_s = malloc(sizeof(*ter_s));

    serport1fd = open(argv[1],O_RDWR | O_NOCTTY | O_NDELAY);//不成为控制终端程序,不受其他程序输出输出影响
    if(serport1fd < 0){
        printf("%s open faild\r\n",argv[1]);
        return -1;
    }

    bzero(ter_s,sizeof(*ter_s));

    ter_s->c_cflag |= CLOCAL | CREAD; //激活本地连接与接受使能

    ter_s->c_cflag &= ~CSIZE;//失能数据位屏蔽
    ter_s->c_cflag |= CS8;//8位数据位

    ter_s->c_cflag &= ~CSTOPB;//1位停止位

    ter_s->c_cflag &= ~PARENB;//无校验位

    ter_s->c_cc[VTIME] = 0;
    ter_s->c_cc[VMIN] = 0;

    /*1 VMIN> 0 && VTIME> 0
        VMIN为最少读取的字符数,当读取到一个字符后,会启动一个定时器,在定时器超时事前,如果已经读取到了VMIN个字符,则read返回VMIN个字符。如果在接收到VMIN个字符之前,定时器已经超时,则read返回已读取到的字符,注意这个定时器会在每次读取到一个字符后重新启用,即重新开始计时,而且是读取到第一个字节后才启用,也就是说超时的情况下,至少读取到一个字节数据。
        2 VMIN > 0 && VTIME== 0
        在只有读取到VMIN个字符时,read才返回,可能造成read被永久阻塞。
        3 VMIN == 0 && VTIME> 0
        和第一种情况稍有不同,在接收到一个字节时或者定时器超时时,read返回。如果是超时这种情况,read返回值是0。
        4 VMIN == 0 && VTIME== 0
        这种情况下read总是立即就返回,即不会被阻塞。----by 解释粘贴自博客园
    */
    cfsetispeed(ter_s,B115200);//设置输入波特率
    cfsetospeed(ter_s,B115200);//设置输出波特率

    tcflush(serport1fd,TCIFLUSH);//刷清未处理的输入和/或输出

    if(tcsetattr(serport1fd,TCSANOW,ter_s) != 0){
            printf("com set error!\r\n");
    }
    
    
    char buffer[] = {"hello my world!\r\n"};
    char recvbuf[100] = {};

    ser_Data snd_data;
    ser_Data rec_data;
    
    snd_data.serfd = serport1fd;
    rec_data.serfd = serport1fd;

    memcpy(snd_data.databuf,buffer,strlen(buffer));//拷贝发送数据
    
    pthread_create(&pid1,pthread_arr1,sersend,(void *)&snd_data);
    pthread_create(&pid2,pthread_arr2,serrecv,(void *)&rec_data);

   
    ssize_t sizec;
    while(1){

            usleep(100000);
    }
    
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    free(ter_s);
    return 0;
}


void *sersend(void *arg)//串口发送线程函数
{
    ser_Data *snd = (ser_Data *)arg ;
    int ret;
    while(1){
       ret = write(snd->serfd,snd->databuf,strlen(snd->databuf));
       if(ret > 0){
            printf("send success, data is  %s\r\n",snd->databuf);
       }else{
           printf("send error!\r\n");
       }
       usleep(300000);
       /*
       if(发生中断)
       break;//退出
       */
    }
}

void *serrecv(void *arg)//串口发送线程函数
{
 ser_Data *rec= (ser_Data *)arg ;
    int ret;
    while(1){
       ret = read(rec->serfd,rec->databuf,1024);
       if(ret > 0){
            printf("recv success,recv size is %d,data is  %s\r\n",ret,rec->databuf);
       }else{
           /*
            什么也不做
           */
       }
       usleep(1000);
       /*
       if(发生中断)
       break;//退出
       */
    }
}

测试结果如下:
发送测试:

root@lidimini-virtual-machine:/home/lidimini/桌面/tty# gcc -o tty tty.c -lpthread
root@lidimini-virtual-machine:/home/lidimini/桌面/tty# ./tty /dev/pts/1
send success, data is  hello my world!

send success, data is  hello my world!

send success, data is  hello my world!

send success, data is  hello my world!

send success, data is  hello my world!

send success, data is  hello my world!

send success, data is  hello my world!

^C

root@lidimini-virtual-machine:/home/lidimini# cat /dev/pts/2
hello my world!
hello my world!
hello my world!
hello my world!
hello my world!
hello my world!
hello my world!

接收测试:

recv success,recv size is 16,data is  I recv message 

send success, data is  hello my world!

recv success,recv size is 16,data is  I recv message 

send success, data is  hello my world!

recv success,recv size is 16,data is  I recv message 

send success, data is  hello my world!

recv success,recv size is 16,data is  I recv message 

root@lidimini-virtual-machine:/home/lidimini# echo "I recv message " > /dev/pts/2
root@lidimini-virtual-machine:/home/lidimini# echo "I recv message " > /dev/pts/2
root@lidimini-virtual-machine:/home/lidimini# echo "I recv message " > /dev/pts/2
root@lidimini-virtual-machine:/home/lidimini# echo "I recv message " > /dev/pts/2
root@lidimini-virtual-machine:/home/lidimini# echo "I recv message " > /dev/pts/2

写的有些啰嗦,主要是希望多写一些,怕自己忘了,也希望能提供大家一些帮助吧,写的不好,请多担待,有问题请指出,谢谢。

再次感谢每一位前辈付出!

  • 19
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Linux 系统下串口编程可以使用 C 或 C++ 语言来实现。 首先,需要在系统中打开串口设备文件,可以使用 `open` 函数打开串口设备文件,例如: ``` int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { perror("Open serial port failed!"); return -1; } ``` 然后,可以使用 `ioctl` 函数来设置串口的相关参数,例如波特率、数据位、停止位和校验位等。 接着,可以使用 `read` 和 `write` 函数来进行串口的数据读写。 最后,在结束程序时,需要使用 `close` 函数关闭串口设备文件。 需要注意的是,在 Linux 系统中,串口设备文件的名称可能不同,例如 `/dev/ttyS0`、`/dev/ttyUSB0` 等,需要根据实际系统情况进行设置。 ### 回答2: Linux下的串口编程主要是通过Linux提供的串口设备驱动程序进行的,下面是一个基本的串口编程流程: 1. 打开串口设备:通过打开串口设备文件,比如/dev/ttyS0或/dev/ttyUSB0,使用open系统调用函数。 2. 配置串口参数:通过设置串口的各种参数,比如波特率、数据位、停止位、校验等,使用tcgetattr和tcsetattr系统调用函数。 3. 读写数据:使用read和write系统调用函数进行串口数据的读取和写入,可以通过设置超时时间避免阻塞。 4. 关闭串口设备:使用close系统调用函数关闭已打开的串口设备。 在Linux中,还可以使用库函数实现更方便的串口编程,比如使用C语言的termios库或者C++语言的boost库。这些库提供了更高级的接口和封装,简化了串口编程的操作。 对于串口编程,需要注意以下几点: 1. 在进行串口编程前,需要确认串口设备是否可用,并设置相应的用户权限。 2. 在配置串口参数时,需要保证与外部设备相匹配,否则数据的接收和发送可能会出错。 3. 在读写数据时,需要处理数据的格式转换,比如将字符型数据转为整型数据,在进行数据的解析和处理时注意数据的格式。 总之,Linux下的串口编程是基于底层的设备驱动程序进行的,通过打开设备文件、配置参数,使用读写系统调用函数实现对串口的数据读写。使用库函数可以更加方便地进行串口编程。 ### 回答3: Linux串口编程是指在Linux操作系统中使用C或C++语言编写程序来操作串口通信。开发者可以通过串口编程来实现与外部设备的数据交互,例如与传感器、单片机、嵌入式系统等进行数据通信。 在Linux中,串口设备通常以设备文件的形式存在于/dev目录下,例如/dev/ttyS0表示串口0,/dev/ttyUSB0表示USB串口设备。通过打开设备文件,就可以和串口进行读写操作。 在进行串口编程时,需要借助相关的头文件和库函数,例如#include <stdio.h>、#include <fcntl.h>等。首先需要打开串口设备文件,可以使用open函数来打开。然后使用ioctl函数来设置串口参数,例如波特率、数据位、停止位和校验位等。接下来就可以使用read函数来读取串口数据,或者使用write函数来向串口发送数据。 在编程过程中,还可以利用select函数或者线程来监听和处理串口数据的接收和发送。通过select函数能够实现异步操作,当串口有数据可读或者可以写入时,就可以进行相应的处理。 在编写串口程序时,需要注意串口的配置参数和通信协议的一致性。正确设置波特率、数据位、停止位和校验位等参数,以保证数据的正确传输。同时,还需要注意对串口的互斥访问,避免多个程序同时对同一个串口进行操作导致冲突。 总之,Linux下的串口编程是一项常见的嵌入式系统开发任务,通过编写相应的程序代码,可以实现与外部设备的串口通信,实现数据的发送和接收。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值