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> 0VMIN为最少读取的字符数,当读取到一个字符后,会启动一个定时器,在定时器超时事前,如果已经读取到了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> 0VMIN为最少读取的字符数,当读取到一个字符后,会启动一个定时器,在定时器超时事前,如果已经读取到了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

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

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

Linux下串口编程(C语言版本)相关推荐

  1. linux实验串行端口程序设计,Linux下串口编程心得(转)

    最近一段时间,需要完成项目中关于Linux下使用串口的一个部分,现在开帖记录过程点滴. 项目的要求是这样的,Qt应用程序主要完成数据采集和发送功能,一开始在google中海搜关键字"Qt串口 ...

  2. Linux下串口编程

    文章目录 串口 驱动 安装 设备文件 测试代码 编译运行 引用 串口 电平之类的就不说了,串口使用的一般包括rs232全双工,rs422四线全双工,rs485两线半双工,rs485四线全双工几种模式, ...

  3. Linux 下串口编程(C++ 程序设计)

    串口通信是最简单的通信方式.即使在USB 非常流行的今天,依然保留了串行通信的方式.网络上已经有大量关于Linux下 C++ 串口编程的文章,但是我依然要写这篇博文.因为网络上的资料不是内容太多,就是 ...

  4. 【Linux】Linux 下串口编程入门

    目录 串口简介 串口操作 打开串口 设置串口 读写串口 关闭串口 例子 相关主题 串口简介 串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用.常用的串口是 RS-232-C 接口( ...

  5. 串口设置波特率linux函数接口,Linux下串口编程之一:基础设置函数

    1,串口操作需要的头文件 #include /* 标准输入输出定义 */ #include /* 标准函数库定义 */ #include /* Unix 标准函数定义 */ #include #inc ...

  6. Linux下串口编程入门

    1. 串口简介 串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用.常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EI ...

  7. Linux下串口编程基础

    串口知识 串行接口 (SerialInterface) 是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用 ...

  8. Linux下串口编程遇 接收数据错误问题及原因

    近日在调试串口的时候发现,另一设备向我ARM板的串口发送0x0d,我接收之后变成了0x0a,这是问题一:另外当对方向我发送一串数据,如果其中有0x11,那么我总是漏收此数,这是问题二. 由于问题莫名其 ...

  9. Linux下串口编程总结

    1.串口操作需要的头文件 #include <stdio.h> //标准输入输出定义 #include <stdlib.h> //标准函数库定义 #include <un ...

最新文章

  1. Pandas简明教程:五、Pandas简单统计操作及通用方式
  2. Spring的核心思想,总结得非常好!
  3. linux下des加密命令,linux下的DES加密
  4. 怎么做手机的上下滑动_手机视频恢复怎么做?删除时间较久的找回方法
  5. java查找链表中间元素_如何通过Java单次查找链表的中间元素
  6. java获取异常堆栈详情
  7. 查看Linux服务器运行级别命令,linux命令1、如何查看当前的Linux服务器的运行级别?...
  8. Solidity教程一
  9. 优思学院|精益六西格玛中的8大浪费是什么?
  10. 便携主机推荐——ATX电源篇
  11. RK3568的红外遥控
  12. 私域流量社群公司团队管理KPI考核运营方案制度
  13. ddr5和ddr6的显卡插口区别 ddr5和ddr6差距有多大
  14. js圆周运动动画_JavaScript动画实例:沿圆周运动的圆圈
  15. 使用k3s部署轻量Kubernetes集群快速教程
  16. 2018,我们就是星辰大海
  17. 到移动开发大会 听《植物大战僵尸》成功秘诀
  18. Python3 Knn~鸢尾花分类
  19. 用户注册邮箱通知和短信通知详解(php)
  20. 2017-11-11 今天的工作任务

热门文章

  1. Mplayer源代码编译
  2. 详解 Windows自带的MPIO(多路径)
  3. 触摸键盘驱动(STM32,TTP229-B)
  4. UML——实现图(组件图、部署图)
  5. (实验七)Hadoop集群安装之安装数据仓库Hive
  6. 名帖54 隋代 楷书《解方保墓志》
  7. 图像处理:电气设备红外图像分析与处理步骤
  8. java读取classpath_读取classpath资源
  9. 生成四位和六位随机数工具类
  10. 对教培机构来说,搭建教育培训平台有哪些好处?