Linux下的串口通信

news2024/11/25 20:53:58

串口通信


基础知识:

  1. 什么是串口?

alt text

串口全称串行通信接口,是一种常用于电子设备之间通信的异步,全双工接口,典型的串口通信只需要 3 根线,分别是地线 (GND),发送线(TX),接收线(RX)。如下图:

alt text

2.什么是波特率?
串口的通信速率称之为波特率(bandrate),波特率也可以叫码元速率,定义: 单位时间内通过信道传输的码元个数就是波特率,单位: 波特
在数字信道中,一个脉冲信号就是一个码元,码元速率指的是在 1 秒钟内能发送多少个码元
也就是说在数字信道中 1 秒钟内可以发送多少个脉冲信号

alt text

需要注意的是,串口的波特率并不能随意设置,因为通信的双方必须设置相同的波特率才可
以成功通信。如果双方的波特率设置不一样则不能通信成功。另外波特率的值一般在常用的
里面选择,约定俗成。而特殊的波特率有可能需要额外设置。

  1. 什么是比特率?
    比特率:每秒钟传送的比特数,单位是 bit/s
    比特率和波特率的关系:
    比特率=波特率*log2M,M 表示每个码元承载的信息量。
    M 如何理解?
    一个码元就是一个脉冲信号!一个脉冲信号有可能携带 1bit 数据,也有可能携带 2bit 数据,4bit 数据。在二进制系统中,比特率就等于波特率

问题: 🤚
假如串口的波特率是 9600,在二进制系统中一秒钟可以传送多少个字节。
分析:一个字节等于 8 个 bit,也就是 8 个高低电平。在二进制系统中,比特率就等于波特率所以就是 9600/8=1200 个字节

串口通信的格式

  1. 格式 📌
    串口通信的格式分为两种:标准格式非标准格式
    标准格式:起始位(1bit) + 数据位(8bit) + 奇偶校验位(1bit) + 停止位(1bit)

alt text

起始位:数据线上空闲时为 1,拉低代表开始传输数据
数据位:需要发送或者接收的数据。
奇偶校验位:通过对数据中的 1的个数(奇数/偶数) 来校验数据传输是否准确。
停止位:数据传输完成。数据线恢复成 1的状态。

  1. 校验 🖇
    奇校验 (odd parity): 如果数据中有奇数个 1,校验位为 1,否则为 0
    偶校验 (even parity): 如果数据中有偶数个 1,校验位为 1,否则为 0
    0 校验(space parity): 校验位恒为 0,如果为 1表述错误。
    1校验(mark parity):校验位恒为 1,如果为0表示错误

串口的通讯接口

  • 串口只对数据格式有定义,并没有规定接口的电器特性,
  • 如果用高电平代表 1,用低电平代表0,那高电平是多少v呢?低电平又是多少v 呢?所以串口的通信接口类型有很多。
  • 在举个例子:🍐
    如果在串口通信中直接使用处理器引出的接口,电平是 TTL 电平。但是处理器的电平也有可能存在差异,所有某些情况下并不能直接连接。这时就要进行电平转换。
    TTL:transistor transistor logic (我们学习时用的单片机时常常用的TTL电平的,就是那些普通的杜邦线做连接)但是 TTL 的抗干扰能力比较弱,在数据传输的时候很容易出错,所以通信距离也短。往往只用在一个电路板中的俩个不同的芯片通信。既然串口没有规定电器特性,那是不是就可以通过电器特性入手来解决 TTL 的缺点呢?

RS232/协议

RS232 协议是 1970 年美国电子工业协会联合各个厂家共同制定的串行通信标准。这个标准
规定了在串口通信中采用一个标准的连接器,如下图所示,并且在标准中对每个连接器的引
脚和电平也做了规定。

alt text

DB9 引脚说明👆

1脚: 载波检测(DCD)
2脚: 接收数据(RXD)
3 脚: 发出数据(TXD)
4 脚: 数据终端准备好(DTR)
5 脚:信号地线(SG)
6脚: 数据准备好(DSR)
7 脚: 请求发送(RTS)
8 脚: 清除发送(CTS)
9 脚: 振铃指示(RI)

: Recommand Standard 即 RS,推荐标准
特点:逻辑1的电平为-5v 到-15v,逻辑 0的电平为+5v 到+15v。所以抗干扰能力有所增强。通信距离一般可达 15m。

RS485/协议

RS232 通信速度并不快,而且传输距离也不是很远(15m),相对于 TTL 电平来说确实提高
了抗干扰性,但是容易产生共模干扰。

RS485 标准是由电信行业协会和电子工业联盟制定。主要是用来解决超远距离(1200m)以
及更好的抗干扰性能。并且 RS485 具有多站能力,可以利用 RS485 组网。

电平特性
RS485 使用差分信号(抗共模干扰能力强) 进行数据传输,俩线之间的电压差为+2v 到+6v
表示逻辑 1,俩线之间的电压差为-2v 到-6v 表示逻辑 0。

差分信号是用俩跟线来描述是 0 还是 1,所以本质虽然也是串口,但是 485 是半双工,在软件编程中多了一个切换接收或者发送的操作,其他同串口编程一样。

串口子系统框架

alt text

使能内核驱动程序

#默认都是开着的
Device Drivers --->
    Character devices --->
        Serial drivers --->
            8250/16550 and compatible serial support

串口驱动8250🏎,8250通用的串口程序,不光在在ARM中使用,在x86中也有使用,是一个非常完善的驱动程序,相当于一个“汽车轮子”,而我们在造车时就不需要再重复造车轮了。

alt text

串口的应用编程

串口不像SPIIIC的注册流程,不需要在driver层注册驱动,完善控制器的相关结构体和必要的配置信息,而是直接在应用层使用驱动函数即可。

串口的操作流程

了解一下串口的配置结构体: termios 用于控制非同步通信端口。 这个结构包含了至少下列成员:

tcflag_t c_iflag;      /* 输入模式 */
tcflag_t c_oflag;      /* 输出模式 */
tcflag_t c_cflag;      /* 控制模式 */
tcflag_t c_lflag;      /* 本地模式 */
cc_t c_cc[NCCS];       /* 控制字符 */

struct termios
{
unsigned short c_iflag; /* 输入模式标志*/
unsigned short c_oflag; /* 输出模式标志*/
unsigned short c_cflag; /* 控制模式标志*/
unsigned short c_lflag; /*区域模式标志或本地模式标志或局部模式*/
unsigned char c_line; /*行控制line discipline */
unsigned char c_cc[NCC]; /* 控制字符特性*/
};
  1. 步骤一:
    保存原来的串口配置,使用 tcgetattr 函数获取原来的 termio .结构体。
struct termios newtio, oldtio;
tcgetattr(fd, &oldtio);
  1. 步骤二:
    设置 c_cflag,打开 CLOCAL(使能本地连接) 和 CREAD(使能接收) 并清空 CSIZE(数据位)
  2. 步骤三:
    设置 c_cflag 中的数据位。

alt text

  1. 步骤四:
    设置奇偶校验位。
    奇验位:
newtio.c_cflag|= PARENB             //使能奇偶校验
newtio.c_cflag|= PARODD;            //使能奇校验    
newtio.c_iflag|=(INPCK|ISTRIP);      

ISTRIP 的作用是在串口接收数据时,自动剥离每个字符的最高位(第八位),以确保接收到的数据在传递给应用程序之前是正确的七位表示形式。
偶验位:

newtio.c_iflag =(INPCK|ISTRIP)
newtio.c_cflag|= PARENB;
newtio.c_cflag &= ~PARODD;      //清零
break;

无校验:

newtio.c cflag &= ~PARENB;
  1. 步骤五:
    设置波特率,使用函数 cfsetispeed,cfsetospeed。注意波特率前需要加 B,如 B9600

  2. 步骤六:
    设置停止位,如设置 1 位停止位

newtio.c_cflag &= ~CSTOPB;
  1. 步骤七:
    刷新输入队列:使用函数 tcflush(fd,TCIFLUSH);

fd 是使用open函数打开文件得到的串口句柄
TCIFLUSH: 刷新输入队列
TCOFLUSH: 刷新输出队列
TCIOFLUSH:刷新输入输出队列

  1. 步骤八:
    使用函数 tcsetattr(fd,TCSANOW, &newtio);设置配置

TCSANOW: 设置立刻生效
TCSADRIN:发送了所有输出以后设置才生效。

  1. 步骤九:
    使用 open 函数打开串口
open("串口节点", O_RDWR | O_NOCTTY | O_NDELAY);

O_RDWR: 读写权限
O_NOCTTY: 表示不占用终端,即这个程序不会成为这个串口的控制终端
O_NDELAY: 表示非阻塞

源Code

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

#define UART_NAME / dev / ttyS3
int set_uart(int fd, int speed, int bits, char check, int stop)
{
    struct termios newtio, oldtio;
    if (tcgetattr(fd, &oldtio) != 0)
    {
        printf("tcgetattr oldtio is error\n");
        return -1;
    }
    bzero(&newtio, sizeof(newtio));

    newtio.c_cflag |= CLOCAL | CREAD;
    newtio.c_cflag &= ~CSIZE;

    switch (bits)
    {
    case 7:
        newtio.c_cflag |= CS7;
        break;

    case 8:
        newtio.c_cflag |= CS8;
        break;
    }

    switch (check)
    {
    case 'N':
        newtio.c_cflag &= ~PARENB; // 失能
        // newtio.c_iflag &= ~INPCK;  // 不使用
        break;

    case 'E':
        newtio.c_cflag |= PARENB;           // 使能
        newtio.c_cflag &= ~PARODD;          // 使用偶校验
        newtio.c_iflag |= (INPCK | ISTRIP); // 去除输入字符第八位
        break;

    case 'O':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
    }

    switch (speed)
    {
    case 9600:
        cfsetispeed(&newtio, B9600);
        cfsetospeed(&newtio, B9600);
        break;
    case 115200:
        cfsetispeed(&newtio, B115200);
        cfsetospeed(&newtio, B115200);
        break;

    default:
        break;
    }

    switch (stop)
    {
    case 1:
        newtio.c_cflag &= ~CSTOPB;
        break;
    case 2:
        newtio.c_cflag |= CSTOPB;
        break;
    }

    tcflush(fd, TCIFLUSH);
    if (tcsetattr(fd, TCSANOW, &newtio) != 0)
    {
        printf("tcsetattr newtio is error\n");
        return -2;
    }
    return 0;
}

int main()
{
    int fd;
    char buf[128];
    int count;
    fd = open("/dev/ttyS3", O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0)
    {
        printf("open error\n");
        return -1;
    }
    set_uart(fd, 115200, 8, 'N', 1);
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        count = read(fd, buf, sizeof(buf));
        // printf("Having a receive\n");
        buf[count] = '\0';
        if (count > 0)
        {
            printf("read data is %s\n", buf);
        }
    }

    return 0;
}


使用多线程方式进行读写

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

int uartOpen(const char *device, const int baud)
{
    struct termios options;
    speed_t myBaud;
    int status, fd;
    switch (baud)
    {
    case 9600:
        myBaud = B9600;
        break;
    case 115200:
        myBaud = B115200;
        break;
    default:
        return -2;
    }

    fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)
    {
        perror("open");
        return -1;
    }

    fcntl(fd, F_SETFL, O_RDWR); // 设置串口阻塞办法

    // 获取和修改当前选项
    tcgetattr(fd, &options);
    cfmakeraw(&options); // 将终端设置为原始模式8N1无流控

    cfsetispeed(&options, myBaud); // 设置输入波特率
    cfsetospeed(&options, myBaud); // 设置输出波特率

    options.c_cflag |= (CLOCAL | CREAD); 
    options.c_cflag &= ~PARENB;          

    options.c_cflag &= ~CSTOPB; // 设置1位的停止位

    options.c_cflag &= ~CSIZE; // 用数据位掩码清空设置
    options.c_cflag |= CS8;    // 设置8位的数据位

    tcsetattr(fd, TCSANOW, &options); // 使上面新的设置生效
    usleep(10000); // 10ms
    return fd;
}

int uartfd;

/* 向串口发送数据的线程 */
void *sendDatas()
{
    int cnt;
    char *buffer = (char *)malloc(64);
    while (1)
    {

        memset(buffer, '\0', sizeof(buffer));
        printf("send -> ");
        scanf("%s", buffer);
        /*向串口1对应的设备文件写入buffer的数据*/
        cnt = write(uartfd, buffer, strlen(buffer));
        if (cnt < 0)
            printf("Serial send datas error\n");
    }
}

/* 读取串口数据的线程 */
void *recvDatas()
{
    int cnt, readSize;
    char *buffer = (char *)malloc(64);
    while (1)
    {
        /* 判断串口是否有数据 */
        if (ioctl(uartfd, FIONREAD, &cnt) == -1)
        {
            perror("ioctl");
            return 0;
        }
        else
        {
            readSize = read(uartfd, buffer, cnt); // 读取数据到buffer中
            if (readSize > 0)
                printf("recv -> %s", buffer); // 读取成功再打印
         }
        memset(buffer, '\0', sizeof(buffer));
    }
}
int main(int argc, char **argv)
{

    pthread_t sendThread, recvThread;

    uartfd = uartOpen("/dev/ttyS3", 115200);
    if (uartfd == -1)
    {
        printf(" open error\n");
        return -1;
    }
    else
    {
        printf("open  succeed.\n");
    }
    /* 主函数中定义出两个线程用于接收和发送数据 */
    pthread_create(&sendThread, NULL, sendDatas, NULL);
    pthread_create(&recvThread, NULL, recvDatas, NULL);

    /* 主线程每10秒发送心跳包 */
    while (1)
    {
        char alive[] = "I am alive\r\n";
        write(uartfd, alive, strlen(alive));
        sleep(10);
    }
    return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1827511.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Excel/WPS《超级处理器》功能介绍与安装下载

超级处理器是基于Excel或WPS开发的一款插件&#xff0c;拥有近300个功能&#xff0c;非常简单高效的处理表格数据&#xff0c;安装即可使用。 点击此处&#xff1a;超i处理器安装下载 Excel菜单&#xff0c;显示如下图所示&#xff1a; WPS菜单显示&#xff0c;如下图所示&am…

运算符与表达式

运算符和表达式是C语言编程的基础构建块&#xff0c;它们共同构成了C语言程序的核心逻辑和计算。理解和掌握运算符和表达式的相关知识&#xff0c;对于编写高效、易读的C语言代码至关重要。 一、运算符概述 运算符是C语言中用于执行各种操作的符号&#xff0c;它们可以对变量、…

【odoo】右上角的提示(通知服务)

概要 在Odoo中&#xff0c;右上角的提示&#xff08;通知服务&#xff09;用于显示系统消息、警告、错误或信息提示。这个功能通过 JavaScript 在前端实现&#xff0c;并且使用 Odoo Web 框架提供的现成功能。 提示类型 信息提示 (Info)&#xff1a;用于显示普通信息。这类提示…

Windows配置域名映射IP

一、找到 hosts 文件 打开 C:\Windows\System32\drivers\etc 二、添加hosts文件修改、写入权限 右击hosts文件&#xff0c;点击属性 -> 安全 -> Users -> 编辑 -> Users -> 添加修改、写入权限 -> 确定 -> 确定 三、添加映射规则 在文件尾部添加一行映射…

企业数据中台功能介绍

参考视频&#xff1a; 企业级数据中台功能演示_哔哩哔哩_bilibili 具体项目&#xff1a; 平台基础设施: 系统管理,系统监控&#xff08;登录/操作日志&#xff09;,任务调度 元数据管理: 业务系统管理/数据源管理/数据表管理/字段管理 数据源,元数据,数据授权,变更记…

基于Wireshark实现对FTP的抓包分析

基于Wireshark实现对FTP的抓包分析 前言一、虚拟机Win10环境配置二、FileZilla客户端的安装配置下载FileZilla客户端安装FileZilla 三、FileZilla Server安装下载FileZilla Server安装 四、实现对FTP的抓包前置工作实现抓包完成抓包 前言 推荐一个网站给想要了解或者学习人工智…

【纯干货级教程】深度学习/目标检测训练出的loss曲线应该怎么观察分析判断?——以YOLOv5/v7为例

相信很多刚刚接触目标检测系列算法小伙伴跑深度学习算法时会有许多困惑&#xff0c;比如训练得出的loss曲线有什么意义&#xff1f;选择哪个算法模型作为baseline、选择哪个参数量/复杂度/深度的模型进行训练最为合适&#xff1f; 本文主要从训练过程中、训练得出的结果文件来…

【保姆级讲解下QT6.3】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Techo TVP技术 沙龙

Techo TVP技术 沙龙 今天参加了 在上海 徐汇 腾讯云大厦 举办的 Techo TVP 技术沙龙&#xff08;主要介绍 AI agent 让我感受很深&#xff09; &#xff0c;那什么是 AI Agent呢&#xff1f; 是一个智能体&#xff0c;由大语言模型驱动&#xff0c;具有自主理解、感知、规划、…

Git冲突解决指南:如何优雅地解决代码合并冲突

在团队协作开发中&#xff0c;使用版本控制系统Git是非常常见的。然而&#xff0c;在多人同时对同一文件进行修改时&#xff0c;就可能出现代码合并冲突。这时就需要我们学会如何优雅地解决这些冲突&#xff0c;保证代码的完整性和质量。本文将为您介绍Git冲突解决的基本原则和…

Elixir学习笔记——进程(Processes)

在 Elixir 中&#xff0c;所有代码都在进程内运行。进程彼此隔离&#xff0c;彼此并发运行并通过消息传递进行通信。进程不仅是 Elixir 中并发的基础&#xff0c;而且还提供了构建分布式和容错程序的方法。 Elixir 的进程不应与操作系统进程混淆。Elixir 中的进程在内存和 CPU…

【数学代码】幂

Hello!大家好&#xff0c;我是学霸小羊&#xff0c;今天来讲讲幂。 求几个相同因数的积的运算&#xff0c;叫做乘方&#xff0c;乘方的结果叫做幂。 a^n&#xff0c;读作 “ a的n次方 ” 或 “ a的n次方幂”&#xff0c;a叫做底数&#xff0c;n叫做指数。 对于底数、指数和幂…

开源高效API管理工具:RAP

RAP&#xff1a;简化API开发&#xff0c;提升团队协作效率- 精选真开源&#xff0c;释放新价值。 概览 RAP&#xff08;RESTful API Project&#xff09;是一个开源的API管理工具&#xff0c;由阿里巴巴团队开发并维护。它旨在帮助前后端开发人员通过一个统一的平台来设计、开…

《分析模式》漫谈03- Unified Method并不是RUP

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 在《分析模式》第2章 &#xff0c;Fowler提到了“Rational Softwares Unified Method”&#xff0c; 而且给出了引用的参考文献&#xff0c;是Rational公司的一份文档&#xff1a; 200…

CSS入门到精通——表格样式

目录 表格边框 任务描述 相关知识 表格边框 折叠边框 编程要求 表格颜色、文字与大小 任务描述 相关知识 表格颜色 表格文字对齐与文字粗细 表格宽度和高度 任务要求 表格边框 任务描述 本关任务&#xff1a;在本关中&#xff0c;我们将学习如何使用CSS设置表格样…

基于carsim的线控转向仿真(1)--carsim车辆模型目标角度跟踪

一、Rwa转向执行总成建模 Rwa包括齿轮齿条机构、转向组件以及转向执行电机&#xff1b;如下图&#xff0c;电机输出轴通过齿轮减速增扭后&#xff0c;再经过一个半径为rp的小齿轮&#xff0c;直接带动齿条左右移动。齿条的移动通过转向摇臂&#xff0c;带动车轮转动&#xff0c…

Django初学者指南

文章目录 Django初学者指南1 Django简介1.1 Django的历史1.2 使用Django的知名网站1.4 Django的主要特点1.5 Django的工作原理 2 Django 使用2.1 Django 支持的 Python 版本2.2 Django 版本 3 Django 开发 Web 程序3.1 安装Django3.2 创建Django项目3.3 运行开发服务器3.4 创建…

【C++】【期末考】【基本概念和语法】概括总结——期末速成

目录 1. C简介 C的历史与发展 C的特点与优势 2. 基本语法 注释 数据类型与变量 常量 运算符 输入与输出 3. 控制结构 条件语句 循环语句 4. 函数 函数定义与声明 参数传递 返回值 函数重载 5. 数组与字符串 一维数组 多维数组 字符串处理 6. 指针 指针的…

数字电路中二进制的数据表达

文章目录 1. 二进制数据表达 1.1 二进制简介 1.2 用二进制表达文字 1.2.1 最开始的表达方式 1.2.2 通讯系统的编码和解码 1.2.3 集成电路 1.2.4 ASCII编码 1.2.5 GBK编码 1.2.6 Unicode编码 2. 用二进制表达图像 2.1 图片像素化 2.2 像素数字化 2.3 二值图像 2.4…

HTML+CSS 旋转呼吸加载器

效果演示 实现了一个旋转加载动画效果&#xff0c;包括一个圆形的加载框和两个不同颜色的圆形旋转动画。加载框和动画都使用了CSS的动画属性&#xff0c;实现了旋转和缩放的效果。整个加载动画的样式比较简单&#xff0c;使用了黑色和黄色的背景色&#xff0c;以及白色的文本颜…