一、串口通信基础

1. 串口通信基本概念

  • 串口通信(Serial Communication)是计算机与外设之间进行数据交换的一种通信方式,数据通过串行方式逐位传输。
  • 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了单片机系统的硬件实力。
(1)同步通信和异步通信

发送方和接收方按照同一个时钟节拍工作就叫同步。发送方和接收方没有统一的时钟节拍、而各自按照自己的节拍工作就叫异步。

同步通信中,通信双方按照统一节拍工作,所以配合很好;一般需要发送方给接收方发送信息同时发送时钟信号,接收方根据发送方给它的时钟信号来安排自己的节奏。同步通信用在通信双方信息交换频率固定,或者经常通信时。

异步通信,又叫异步通知。在双方通信的频率不固定时(有时3ms收发一次,有时3天才收发一次),不适合使用同步通信,而适合异步通信。异步通信时接收方不必一直在意发送方,发送方需要发送信息时会首先给接收方一个信息开始的起始信号,接收方接收到起始信号后就认为后面紧跟着的就是有效信息,才会开始注意接收信息,直到收到发送方发过来的结束标志。

(2)电平信号和差分信号

 电平信号和差分信号是用来描述通信线路传输方式的。也就是说如何在通信线路上表示1和0。

电平信号的传输线中有一个参考电平线(一般是GND),然后信号线上的信号值是由信号线电平和参考电平线的电压差决定。

差分信号的传输线中没有参考电平线,所有都是信号线,然后1和0的表达靠信号线之间的电压差。

电平信号的2根通信线之间的电平差异容易受到干扰,传输容易失败;差分信号不容易受到干扰,因此传输质量比较稳定。现代通信一般都使用差分信号,电平信号几乎没有了。

在相同根数的通信线下,差分信号比电平信号要快,因为差分信号抗干扰能力强,因此1个发送周期更短。

(3)并行接口和串行接口

串行、并行主要是考虑通信线的根数,就是发送方和接收方同时可以传递的信息量的多少。

在电平信号下,”1根参考电平线+1根信号线“可以传递1位二进制;”1根参考电平线+2根信号线“可以同时发送2位二进制;如果想同时发送8位二进制就需要9根线。

在差分信号下,2根线(彼此差分)可以同时发送1位二进制;如果需要同时发送8位二进制就需要16根线。

串行接口使用的更加广泛,因为更省信号线,而且对传输线的要求更低、成本更低;而且串行时可以通过提高通信速度来提高总体通信性能,不一定非得要并行。

串行通讯是指仅用一根接收线和一根发送线就能将数据以位进行传输的一种通讯方式。尽管串行通讯的比按字节传输的并行通信慢,但是串口可以在仅仅使用两根线的情况下就能实现数据的传输。典型的串口通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,所以端口能够在一根线上发送数据同时在另一根线上接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶的校验。对于两个需要进行串口通信的端口,这些参数必须匹配,这也是能够实现串口通讯的前提。

  • 串口通信是一种设备间常用的串行通信方式,串口按位(bit)发送和接收字节。尽管比字节(byte)的串行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。

 2.串口通信的特点

(1)异步

串口通信的发送方和接收方之间没有统一的时钟信号。

(2)电平信号

  串口通信出现的时间较早、速率较低、传输的距离较近,所有干扰不太明显,因此当时使用了电平信号传输。后期出现的传输协议都改成了差分信号传输了。

(3)串行通信

串口通信每次同时只能传输1个二进制位。

(4)串口的基本参数

端口(Port Name):

波特率(Baud Rate):每秒传输的比特数,也就是串口通信时每秒钟可以传输多少个二进制位譬如,每秒钟可以传输9600个二进制位(传输一个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600,常用的波特率有1200、2400、9600、115200、640000等

串口通信的波特率不能随意设定,而应该是在一些值中去选择。通信双方必须事先设定相同的波特率才能成功通信,如果发送方和接收方按照不同的波特率通信则根本收不到,因此波特率最好是大家熟知的而不是随意指定的;常用的波特率经过长久发展,就形成了共识,大家常用的就是9600或者115200(低端单片机如51常用9600,高端单片机和嵌入式SOC常用115200)

数据位(Data Bits):每个数据包包含的位数,常见值为 8。

校验位(Parity):用于检测传输错误的校验方式,常见值有无校验(None)、奇校验(Odd)、偶校验(Even)。

停止位(Stop Bits):标志一帧数据结束的位数,常见值为 1 或 2。

流控制(Flow Control):数据流的控制方式,常见值为无(None)。

(5)串口通信数据结构
  • 串口通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位及停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。

串口的数据结构图

(1)串口通信时,收发是一个周期一个周期进行的,每个周期传输n个二进制位。这一个周期就叫做一个通信单元,一个通信单元由:起始位+数据位+奇偶校验位+停止位组成的。

(2)起始位:表示发送方要开始发送一个通信单元,起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。必须是持续一个比特时间的逻辑0电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。

(3)数据位:是一个通信单元中发送的有效信息位,是本次通信真正要发送的有效数据,串口通信一次发送多少位有效数据是可以设定的(可选的有6、7、8、9,一般都是选择8位数据位,因为一般通过串口发送的文字信息都是ASCII码编码,而ASCII码中一个字符刚好编码为8位)。传输数据时先传送字符的低位,后传送字符的高位。

(4)奇偶校验位:奇偶校验位仅占一位,用于进行奇校验或偶校验,奇偶检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个逻辑高位;如果是偶校验,需要保证传输的数据总共有偶数个逻辑高位。一般是用crc16算法来校验数据位,以防止数据位出错的。

1)奇偶校验
假设数据字节为 01010101(二进制形式),其1的个数是4(偶数)。

偶校验:校验位应为0,使得总的1的个数(4 + 0)保持偶数。

奇校验:校验位应为1,使得总的1的个数(4 + 1)变成奇数。

(5)停止位:是发送方用来表示本通信单元结束标志的,停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。常见的有1位停止位、1.5位停止位、2位停止位等,一般使用的是1位停止位它一定是逻辑1电平,标志着传输一个字符的结束。

二、modbus通信协议

1.Modbus 协议概述

  • Modbus 是一种工业通信协议,最初由施耐德电气开发于 1979 年。它是一种主从式通信协议,广泛应用于工业自动化领域中的设备间数据交换1。
1什么是协议

在我们的单片机之间互相通信,以及单片机和上位机通信中,规定了不同的内容规范,这个规范是通信的双方都需要遵守的,这样就可以实现两者的通信。

而这个协议规范可以有很多种,来适应不同的设备以及通信要求等,我们常见的就有IIC SPI UART串口通信协议等等。而Modbus也是一个串行通信协议。

(2)RS-485 RS-232串口

RS232,RS485是一种电平标准,数据在通信双方之间传输,本质是传输物理的电平 比方说传输5V的电压 -1V的电压信号,这些物理信号在传输过程中会受到很多干扰,比方说你传输一个5V的电压,到了接收端可能就变成了4.8V,并且通信的双方高低电平的参考电压可能不同。

那么这个时候就需要一个电平标准,来判断多少V的电压是高电平 1,多少V的电压是低电平 0 这就诞生了 RS-485 RS-232

1)RS232:是电子工业协会(Electronic Industries Association,EIA) 制定的异步传输标准接口,同时对应着电平标准和通信协议(时序),其电平标准:+3V~+15V对应0,-3V~-15V对应1。

全双工

逻辑1:-15V–5V

逻辑0:+3V–+15V

2)RS485:RS485是一种串口接口标准,为了长距离传输采用差分方式传输,传输的是差分信号,抗干扰能力比RS232强很多。两线压差为-2~-6V表示0,两线压差为+2~+6V表示1

半双工

逻辑1:+2V~+6V

逻辑0: -2V~ -6V

注意485的电平指的是485-A和485-B两根传输线,两线间的电压差。而不是传输线上的电压如果还想继续了解可以看这个博主的文章:

USB转串口 TTL RS-232 RS-485 COM口 UART区别_485转usb与ttl转usb-CSDN博客

2.Modbus 通信过程

Modbus是一主多从的通信协议从机不会自己发送消息给主站,只能回复从主机发送的消息请求。

Modbus通信中只有一个设备可以发送请求。其他从设备接收主机发送的数据来进行响应,从机是任何外围设备,如I/O传感器,阀门,网络驱动器,或其他测量类型的设备。从站处理信息和使用Modbus将其数据发送给主站。

也就是说,不能Modbus同步进行通信,主机在同一时间内只能向一个从机发送请求,总线上每次只有一个数据进行传输,即主机发送,从机应答,主机不发送,总线上就没有数据通信。

并且,Modbus并没有忙机制判断,比方说主机给从机发送命令, 从机没有收到或者正在处理其他东西,这时候就不能响应主机,因为modbus的总线只是传输数据,没有其他仲裁机制,所以需要通过软件的方式来判断是否正常接收。

3.Modbus 存储地址

(1)采用从机存储数据文件可以分为只读(-r)和读写(-wr)两种类型并且存储的数据类型可以分为布尔量和16位寄存器

1)布尔量:比如IO口的电平高低,灯的开关状态等。

2)16位寄存器比如 传感器的温度数据,存储的密码等

(2Modbus协议规定了4个存储区 分别是0 1 3 4区 其中0区和4区是可读可写,1区和3区是只读。

区号

名称

读写

地址范围

0区

输出线圈

可读可写布尔量

00001-09999

1区

输入线圈

只读布尔量

10001-19999

3区

输入寄存器

只读寄存器

30001-39999

4区

保持寄存器

可读可写寄存器

40001-49999

并且Modbus还给每个区都划分了地址范围 主机向从机获取数据时,只需要告诉从机数据的起始地址,还有获取多少字节的数据,从机就可以发送数据给主机Modbus数据模型规定了具体的地址范围,每一个从机,都有实际的物理存储,跟modbus的存储区相对应,主机读写从机的存储区,实际上就是对从机设备对应的实际存储空间进行读写。

4.Modbus 协议类型

Modbus可以在各种介质上传输,那么他的传输模式也分为三种。包括ASCII、RTU(远程终端控制系统)、TCP三种报文类型

串行端口存在多个版本的Modbus协议,而最常见的是下面种:

  1. Modbus RTU是一种紧凑的,十六进制表示数据的方式RTU格式后续的命令/数据带有循环冗余校验的校验和使用RS232或者RS485/RS422接口,通讯方式是串口通讯,是直接传输二进制数值的通讯,工业领域用得最多

  1. Modbus ASCII是一种采用Ascii码表示数据,并且每个8Bit 字节都作为两个ASCII字符发送的表示方式ASCII格式采用纵向冗余校验的校验和。使用RS232或者RS485/RS422接口,通讯方式是串口通讯,Modbus ASCII比前两者少用,支持Modbus ASCII协议的设备一定也会支持Modbus RTU协议

3)Modbus-Tcp通过以太网传输时使用TCP,这种模式不使用校验,因为TCP协议是一个面向连接的可靠协议。使用网口通讯,更多是用于快速网络设备,如机器人,上位机视觉

Modbus协议使用串口传输时可以选择RTU或ASCII模式,并规定了消息、数据结构、命令和应答方式并需要对数据进行校验。ASCII 模式采用LRC校验,RTU模式采用16 位CRC校验。常用的就是RTU模式,ASCII一般很少,后面也将以RTU为例 

5.Modbus 协议格式

(1)一个报文就是一帧数据,一个数据帧就一个报文: 指的是一串完整的指令数据,本质就是一串数据。Modbus报文是指主机发送给从机的一帧数据,其中包含着从机的地址,主机想执行的操作,校验码等内容

从站地址

功能码

数据

CRC/LRC

1 byte

1 byte

N bytes

2 bytes

1)从机地址: 每个从机都有唯一地址,占用一个字节,范围0-255,其中有效范围是1-247,其中255是广播地址(广播就是对所有从机发送应答)

2)功能码: 占用一个字节,功能码的意义就是,知道这个指令是干啥的,比如你可以查询从机的数据,也可以修改从机的数据,所以不同功能码对应不同功能

Modbus规定了多个功能,那么为了方便的使用这些功能,我们给每个功能都设定一个功能码,也就是指代码。协议同时规定了二十几种功能码,但是常用的只有8种,用于对存储区的读写,如下表所示

功能码

功能说明

01H

读取输出线圈

02H

读取输入线圈

03H

读取保持寄存器

04H

读取输入寄存器

05H

写入单线圈

06H

写入单寄存器

0FH

写入多线圈

10H

写入多寄存器

当然我们用的最多的就是03和06 一个是读取数据,一个是修改数据

3)数据: 根据功能码不同,有不同功能,比方说功能码是查询从机的数据,这里就是查询数据的地址和查询字节数等

4)CRC校验: 在数据传输过程中可能数据会发生错误,CRC检验检测接收的数据是否正确。CRC校验域占用两个字节包含了一个16位的二进制值。CRC值由传输设备计算出来,然后附加到数据帧上,接收设备在接收数据时重新计算CRC值,然后与接收到的CRC域中的值进行比较,如果这两个值不相等,就发生了错误

 CRC计算方法:

    1.预置1个16位的寄存器为十六进制FFFF(即全为1);称此寄存器为CRC寄存器;

    2.把第一个8位二进制数据(既通讯信息帧的第一个字节)与16位的CRC寄存器的低8位相异或,把结果放于CRC寄存器;

    3.把CRC寄存器的内容右移一位(朝低位)用0填补最高位,并检查右移后的移出位;

    4.如果移出位为0:重复第3步(再次右移一位);如果移出位为1:CRC寄存器与多项式A001(1010 0000 0000 0001)进行异或;

   5.重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;

   6.重复步骤2到步骤5,进行通讯信息帧下一个字节的处理;

   7.将该通讯信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换;

算法详细原理及手写演算可参考

Modbus-crc16校验原理和方法(含测试代码以及演算过程)_crc16modbus-CSDN博客

三、libmodbus库使用

1.libmodbus库介绍

libmodbus,是一个基于C语言实现的Modbus驱动库,作者是Stephane,支持Linux, Mac OS X, FreeBSD, QNX and Win32操作系统,主要应用在PC上,用来开发上位机,也可以对源代码进行交叉编译,以适配更多的平台,比如ARM Linux。源代码开源,遵循 LGPL-2.1 许可

官方网站

www.libmodbus.org

2. libmodbus库安装

参考链接LibModbus移植_libmodbus交叉编译-CSDN博客

通过以上步骤,我们成功将LibModbus移植到了嵌入式Linux系统中,并通过测试程序验证了其功能。

LibModbus的移植过程主要包括下载源文件、交叉编译、复制动态链接库到开发板以及编写测试程序。在移植过程中,需要注意配置编译选项时指定正确的交叉编译工具链前缀和安装路径,以及确保测试程序中正确配置了串口参数和设备连接。

LibModbus的移植为嵌入式系统中Modbus协议的应用提供了基础支持,使得嵌入式设备能够方便地与其他Modbus设备进行通信,实现数据交互和设备控制等功能,进一步拓展了嵌入式系统在工业自动化领域的应用范围

3. 核心功能

(1). 创建 Modbus 上下文

libmodbus 使用上下文(Context)来管理 Modbus 通信。根据通信方式的不同,可以选择创建 RTU 或 TCP 上下文。

1)创建 RTU 上下文

modbus_t *ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);

if (!ctx) {

    fprintf(stderr, "Failed to create RTU context\n");

    return -1;

}

2)TCP 上下文

modbus_t *ctx = modbus_new_tcp("127.0.0.1", 502);

if (!ctx) {

    fprintf(stderr, "Failed to create TCP context\n");

    return -1;

}

(2). 设置从站地址

在 Modbus 通信中,主站需要指定从站地址。

int slave_id = 1;

if (modbus_set_slave(ctx, slave_id) == -1) {

    fprintf(stderr, "Failed to set slave ID\n");

    return -1;

}

(3). 连接 Modbus 设备

在创建上下文后,需要连接到 Modbus 设备。

if (modbus_connect(ctx) == -1) {

    fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));

    modbus_free(ctx);

    return -1;

}

(4). 读写寄存器

libmodbus 提供了以下函数用于读写寄存器

modbus_read_bits

读取线圈状态(0x01 功能码)

modbus_read_input_bits

读取离散输入状态(0x02 功能码)

modbus_read_registers

读取保持寄存器(0x03 功能码)

modbus_read_input_registers

读取输入寄存器(0x04 功能码)

modbus_write_bit

写入单个线圈(0x05 功能码)

modbus_write_register

写入单个保持寄存器(0x06 功能码)

modbus_write_bits

写入多个线圈(0x0F 功能码)

modbus_write_registers

写入多个保持寄存器(0x10 功能码)

1)读取保持寄存器

uint16_t tab_reg[10];

int rc = modbus_read_registers(ctx, 0, 10, tab_reg);

if (rc == -1) {

    fprintf(stderr, "Failed to read registers: %s\n", modbus_strerror(errno));

    return -1;

}

for (int i = 0; i < rc; i++) {

    printf("Register %d: %d\n", i, tab_reg[i]);

}

2)写入保持寄存器

uint16_t value = 1234;

if (modbus_write_register(ctx, 0, value) == -1) {

    fprintf(stderr, "Failed to write register: %s\n", modbus_strerror(errno));

    return -1;

}

(5). 关闭连接和释放资源

在通信结束后,需要关闭连接并释放资源。

modbus_close(ctx);

modbus_free(ctx);

4.相比原始modbus格式的优点

对比维度

原始Modbus格式发送

libmodbus库

协议构造

需手动构造完整帧(地址+功能码+数据+CRC)

自动生成

错误处理

需自行实现CRC校验和异常响应处理

内置完善机制

开发效率

开发周期长

快速实现功能

维护性

代码难以维护

API标准化,易于维护

跨平台

需针对不同平台适配串口/TCP操作

统一接口,自动适配

// 原始方式读取保持寄存器(需自行构造帧):

uint8_t request[] = {SLAVE_ID, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A};

write(serial_fd, request, sizeof(request));

// libmodbus方式:

modbus_read_registers(ctx, 0, 1, &value);

5.未知参数时的探测方法

(1) 站地址探测

for(int addr = 1; addr <= 247; addr++) {

    modbus_set_slave(ctx, addr);

    if(modbus_read_registers(ctx, 0, 1, &test_data) == 1) {

        printf("Found device at address %d\n", addr);

        break;

    }

}

(2) 功能码探测

const uint8_t possible_fc[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};

for(int i = 0; i < sizeof(possible_fc)/sizeof(possible_fc[0]); i++) {

    uint8_t response[MAX_RESPONSE];

    int len = modbus_send_raw_request(ctx, possible_fc[i], 0x0000, 0x0001, response);

    if(len > 0) {

        printf("Supported function code: 0x%02X\n", possible_fc[i]);

    }

}

(3)寄存器扫描

// 扫描可能的寄存器范围

for(int reg = 0; reg < 100; reg++) {

    if(modbus_read_registers(ctx, reg, 1, &data) == 1) {

        printf("Valid register found at %d, value: %d\n", reg, data);

    }

}

6.实际应用场景

1工业自动化

libmodbus 可以用于与 PLC、传感器、执行器等设备进行通信,实现数据采集和控制

2 能源管理

通过 Modbus 协议,可以读取电表、水表等设备的能耗数据,实现能源管理。

3 智能家居

libmodbus 可以用于与智能家居设备(如智能灯光、温控器)进行通信,实现远程控制。

libmodbus 是一个功能强大且易于使用的 Modbus 通信库,支持 RTU 和 TCP 协议,适用于多种应用场景。通过它,开发者可以轻松实现 Modbus 主站和从站功能,并与工业设备进行高效通信。

参考资料

  1. 什么是串口通信?-CSDN博客
  2. 你真的懂吗系列——串口通信-CSDN博客
  3. Modbus-crc16校验原理和方法(含测试代码以及演算过程)_crc16modbus-CSDN博客
  4. 串口通信原理与实践-CSDN博客
  5. 详解Modbus通信协议---清晰易懂-CSDN博客
  6. 工作:MODBUS通讯协议知识_modbus协议格式-CSDN博客
  7. LibModbus移植_libmodbus交叉编译-CSDN博客
  8. 使用 libmodbus 实现 Modbus 通信-CSDN博客
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐