STM32与i.MX6ULL的RS485通信(Modbus)

目录

一、RS485介绍

1. RS485应用领域

2. RS485协议特点

3. Modbus协议介绍

协议架构

传输模式

Modbus常用功能码

Modbus RTU模式下的帧报文(下面介绍常用的几种)

二、Linux下的RS485开发

1. Linux下串口通信

串口通信时序

基本串口参数

逻辑信号

2. i.MX6ULL下串口编程

3. Modbus编程

用于Modbus测试的几款软件:

i.MX6ULL的modbus做主机,实现功能码0x03,0x06,0x10

i.MX6ULL的modbus做从机,实现功能码0x03,0x06,0x10

4. LibModbus库编程实现

libmodbus库rtu主机和从机通信

libmodbus库tcp服务端和客户端通信

三、STM32下的RS485开发

1. 单片机下串口编程

STM32CubeMx配置

STM32CubeIDE编写代码

2. Modbus编程

STM32CubeMx配置

 STM32CubeIDE编写代码

3. FreeModbus库编程实现

移植FreeModbus库

四、STM32与i.MX6ULL的RS485通信(Modbus)


一、RS485介绍

RS-485是美国电子工业协会(EIA)在1983年批准了一个新的平衡传输标准,EIA一开始将RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,已将RS改为EIA/TIA。目前标准名称为TIA-485,但工程师及应用指南仍继续使用RS-485来称呼此标准。

1. RS485应用领域

工业自动化:在工业自动化领域,RS485接口被广泛用于连接各种传感器、执行器、PLC(可编程逻辑控制器)等设备,实现数据采集、监控和控制。其高速传输、长距离传输以及抗干扰能力使其适用于工厂自动化、过程控制系统和机器人控制等应用。

楼宇自动化:RS485接口在楼宇自动化系统中也发挥着重要作用。通过RS485总线连接各个子系统,如照明控制、空调控制、安防系统等,可以实现集中管理和控制。其长距离传输和多点通信特性使其能够覆盖大型建筑物,并且方便扩展和升级。

智能交通:RS485接口在智能交通领域也得到了广泛应用。例如,在交通信号控制系统中,通过RS485接口可以实现交通信号灯与交通监控中心之间的数据传输和控制。此外,RS485接口还可以用于电子警察系统、智能停车系统等领域的数据传输。

智能建筑:在智能建筑领域,RS485接口同样发挥着重要作用。通过RS485总线连接智能设备(如智能电表、智能水表等),可以实现远程监控和数据采集。这对于提高建筑能源利用效率、降低能耗具有重要意义。

仪器仪表:RS485接口被广泛用于仪器仪表领域的数据采集和通信。许多传感器、仪器和测量设备都支持RS485接口,通过该接口可以实现数据的实时传输和监控。

2. RS485协议特点

  1. 抗干扰能力强:RS485接口采用差分信号传输方式,可以有效地抵抗电磁干扰和噪声干扰,确保数据传输的稳定性和可靠性。
  2. 传输距离远:RS485接口的最大传输距离可达1200米(在特定条件下),这使得它在需要远距离数据传输的应用中具有独特的优势。
  3. 传输速率高:RS485接口的传输速率在短距离内可达35Mbps,即使在1200米的距离下,传输速率仍可达100Kbps或更高(具体取决于波特率和电缆质量)。这种高速传输特性使得RS485能够满足各种实时性要求较高的应用需求。
  4. 支持节点多:RS485接口一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,甚至最大可以支持到400个节点。这种多点通信特性使得RS485能够满足各种复杂网络的需求。
  5. 接口电平低:RS485的电气特性使得其接口电路不易损坏,同时也方便与TTL电路连接。RS485采用负逻辑,逻辑“0”以两线间的电压差为+(2~6)V表示,逻辑“1”以两线间的电压差为-(2~6)表示。
  6. 半双工通信:RS485接口支持半双工串行通信模式,即在同一时间内只能进行数据发送或接收操作。虽然这在一定程度上降低了数据传输的效率,但简化了电路设计并降低了成本。
  7. 总线结构:RS485接口采用总线结构进行数据传输,可以方便地实现多台设备之间的数据通信和共享。

3. Modbus协议介绍

Modbus协议是一种广泛应用于工业自动化系统中的通信协议,它最初由Modicon公司(现为施耐德电气Schneider Electric)于1979年发布。

协议架构

  1. 主从结构:网络中有一个主设备向多个从设备发起请求,从设备响应请求
  2. Modbus协议分为应用层、数据链路层和物理层
  • 应用层:定义了Modbus消息的格式和内容,包含功能码、数据地址、数据长度以及数据内容等信息;
  • 数据链路层:负责传输Modbus消息,包括帧起始符、帧结束符、校验位等;
  • 物理层:定义了消息的物理传输方式,如电气特性、传输速率等。Modbus协议在物理层上支持多种通信方式,如RS-232、RS-422和RS-485等。

传输模式

Modbus协议支持多种传输模式,包括RTU(远程终端单元)模式、ASCII模式和TCP/IP(如以太网)模式。

  1. RTU模式:采用二进制表示数据,速度快且可靠;包含设备地址、功能码、数据区(长度根据功能码决定)、CRC校验码;比ASCII模式具有更高的数据传输速率和效率。
  2. ASCII模式:采用ASCII码格式,在RTU模式基础上增加了起始字符、结束字符、和LRC校验码。
  3. TCP/IP模式:以TCP报文的形式封装Modbus数据,包含设备地址(在TCP连接中隐含,不再在报文中携带)、功能码、数据区;存在Modbus/TCP变种,这种方式不需要校验和计算,具有速度快、网络连接稳定等优点。

Modbus常用功能码

功能码(十六进制)

名称

功能描述

01 (0x01)

读取线圈状态(Read Coils)

用于读取从站设备中线圈的ON/OFF状态

02 (0x02)

读取离散输入状态(Read Discrete Inputs)

读取从站设备中离散输入的状态

03 (0x03)

读取保持寄存器(Read Holding Registers)

读取从站设备中一个或多个保持寄存器的值

04 (0x04)

读取输入寄存器(Read Input Registers)

读取从站设备中一个或多个输入寄存器的值(只读)

05 (0x05)

写单个线圈(Write Single Coil)

将单个线圈设置为ON或OFF状态

06 (0x06)

写单个保持寄存器(Write Single Register)

写入单个保持寄存器的值

08 (0x08)

诊断功能(Diagnostic)

用于检测主设备和从设备之间的通信故障或从设备的内部故障

15 (0x0F)

写多个线圈(Write Multiple Coils)

同时写入多个线圈的状态

16 (0x10)

写多个保持寄存器(Write Multiple Registers)

同时写入多个保持寄存器的值

Modbus RTU模式下的帧报文(下面介绍常用的几种)

  • Modbus协议规定每个寄存器大小是16位的,即使用uint16_t类型

  1. 读保存寄存器功能码0x03:
  • 主机发送帧报文格式

字节序号

1

2

3

4

5

6

7

8

定义

ADDR

CMD

MSB_ADDR

LSB_ADDR

MSB_CNT

LSB_CNT

CRC_LOW

CRC_HIGH

解释

从节点地址

功能码

寄存器起始地址

寄存器个数

CRC校验码

  • 从机正常应答帧报文格式

字节序号

1

2

3

5

6

...

L+1

L+2

L+3

L+4

定义

ADDR

CMD

Length

MSB_VAL

LSB_VAL

...

MSB_VAL

LSB_VAL

CRC_LOW

CRC_HIGH

解释

从节点地址

功能码

发送字节数L=n*2

第一个寄存器值

...

第N个寄存器值

CRC校验码


  1. 写单个保存寄存器功能码0x06:
  • 主机发送帧报文格式

字节序号

1

2

3

4

5

6

7

8

定义

ADDR

CMD

MSB_ADDR

LSB_ADDR

MSB_VAL

LSB_VAL

CRC_LOW

CRC_HIGH

解释

从节点地址

功能码

寄存器起始地址

寄存器值

CRC校验码

  • 从机正常应答帧报文格式 (同主机发送帧报文格式一样)

字节序号

1

2

3

4

5

6

7

8

定义

ADDR

CMD

MSB_ADDR

LSB_ADDR

MSB_VAL

LSB_VAL

CRC_LOW

CRC_HIGH

解释

从节点地址

功能码

寄存器起始地址

寄存器值

CRC校验码


  1. 写多个保存寄存器功能码0x10:
  • 主机发送帧报文格式

字节序号

1

2

3

4

5

6

7

8

...

L+6

L+7

L+8

定义

ADDR

CMD

MSB_ADDR

LSB_ADDR

MSB_VAL

LSB_VAL

Length

Data1

...

DataN

CRC_LOW

CRC_HIGH

解释

从节点地址

功能码

寄存器起始地址

寄存器个数

发送字节数L=n/8+(1)

第一个字节数据值

...

第N个字节数据值

CRC校验码

  • 从机正常应答帧报文格式

字节序号

1

2

3

5

6

7

8

定义

ADDR

CMD

MSB_ADDR

MSB_VAL

LSB_VAL

CRC_LOW

CRC_HIGH

解释

从节点地址

功能码

寄存器起始地址

寄存器值

CRC校验码


从机异常应答帧报文格式:

字节序号

1

2

3

4

5

定义

ADDR

CMD+128

ErrCode

CRC_LOW

CRC_HIGH

解释

从节点地址

功能码+128

错误码

CRC校验码

二、Linux下的RS485开发

1. Linux下串口通信

串口通信时序

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

基本串口参数

在Linux下进行串口通信时,需要了解并配置一些基本的串口参数,这些参数共同定义了数据传输的格式和速率。这些参数包括波特率(Baud Rate)、起始位(Start Bit)、数据位(Data Bits)、停止位(Stop Bit)、以及校验位(Parity Bit)

  • 波特率(Baud Rate):串口通信是一种异步通信方式,收发双方并没有同步时钟信号来规约一个bit的数据发送电平维持多长时间,这样只能靠收发双方的速率来同步收发数据。波特率定义了每秒传输的比特(bit)数量,它决定了数据传输的速率。常见的波特率有9600、19200、38400、57600、115200等。较高的波特率意味着数据传输更快,但也可能更容易受到干扰。
  • 起始位(Start Bit):它表示发送方要开始发送一个通信单元,起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。对于串口通信而言总线没有数据传输空闲时维持高电平,一旦产生一个下降沿变成低电平则表示起始信号。
  • 数据位(Data Bits):它一个通信单元中发送的有效信息位,是本次通信真正要发送的有效数据,串口通信一次发送多少位有效数据是可以设定的(通常可以是5、6、7、8或9位)。在大多数情况下,数据位被设置为8位,因为这样可以传输一个标准的ASCII字符。
  • 校验位(Parity Bit):校验位用于检测数据传输过程中的错误。它可以设置为无(None)、奇校验(Odd)、偶校验(Even)等。
  1. 无(None):不发送校验位。
  2. 奇校验(Odd):如果数据位中1的数量是偶数,则校验位为1,使得总位数(包括校验位)中1的数量为奇数。
  3. 偶校验(Even):如果数据位中1的数量是奇数,则校验位为1,使得总位数(包括校验位)中1的数量为偶数。
  • 停止位(Stop Bit):它是发送方用来表示本通信单元结束标志的,停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。常见的有1位停止位、1.5位停止位、2位停止位等,一般使用的是1位停止位。

逻辑信号

RS485采用差分信号负逻辑,+2V~+6V表示“0”,- 6V~- 2V表示“1”:

  • 若是SPACE(逻辑0),线路A信号电压比线路B高;
  • 若是MARK(逻辑1),线路B信号电压比线路A高

由于A、B容易的定义容易混淆一种常用的命名方式是:

  • TX+ / RX+ 或D+来代替B(信号1时为高电平)
  • TX- / RX- 或D-来代替A(信号0时为低电平)

下图列出在RS-485利用“异步开始-停止”方式发送一个字符(0xD3,MSB)时,D+端子及 D−端子上的电压变化。

2. i.MX6ULL下串口编程

  • 首先使能串口驱动/dev/ttymxc3,再用杜邦线将该串口的rx和tx短接
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>

#define DEV_NAME    "/dev/ttymxc3"
#define MSG         "Hello, diancitie"

int main (int argc, char **argv)
{
    // 变量声明
    struct termios  tty;
    int             serial_fd;
    int             bytes;
    char            read_buf[64];

    // 打开串口设备文件
    if ( (serial_fd=open(DEV_NAME, O_RDWR)) == -1)
    {
        printf("Open '%s' failed: %s\n", DEV_NAME, strerror(errno));
        return 0;
    }

    // 获取当前串口属性
    tcgetattr(serial_fd, &tty);

    // 设置串口控制标志位
    tty.c_cflag &= ~PARENB;    // 禁用奇偶校验
    tty.c_cflag &= ~CSTOPB;    // 1位停止位
    tty.c_cflag &= ~CSIZE;     // 清除数据位设置
    tty.c_cflag |= CS8;        // 设置数据位为8位
    tty.c_cflag &= ~CRTSCTS;   // 禁用硬件流控制
    tty.c_cflag |= CREAD | CLOCAL;  // 启用接收器,本地连接

    // 设置本地模式标志位
    tty.c_lflag &= ~ICANON;    // 非规范模式
    tty.c_lflag &= ~ECHO;      // 禁用回显
    tty.c_lflag &= ~ECHOE;     // 禁用擦除字符
    tty.c_lflag &= ~ECHONL;    // 禁用换行符的回显
    tty.c_lflag &= ~ISIG;      // 禁用信号

    // 输入模式标志位
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);  // 禁用软件流控制
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // 禁用特殊输入处理

    // 输出模式标志位
    tty.c_oflag &= ~OPOST;     // 禁用输出处理

    // 设置读取超时参数
    tty.c_cc[VTIME] = 10;      // 读取超时时间为1秒
    tty.c_cc[VMIN] = 0;        // 读取的最小字节数为0

    // 设置波特率
    cfsetispeed(&tty, B115200);
    cfsetospeed(&tty, B115200);

    // 设置串口属性
    tcsetattr(serial_fd, TCSANOW, &tty);

    // 发送数据
    write(serial_fd, MSG, strlen(MSG));
    printf("Send MSG: %s\n", MSG);

    // 清空接收缓冲区并接收数据
    memset(read_buf, 0, sizeof(read_buf));
    if ( (bytes=read(serial_fd, read_buf, sizeof(read_buf))) < 0 )
    {
        printf("Error reading: %s\n", strerror(errno));
        goto cleanup;
    }

    // 打印接收到的数据
    printf("Received MSG: %s\n", read_buf);

cleanup:
    // 关闭串口
    close(serial_fd);
    return 0;
}
  • 串口初始化 (tcgetattr, cfsetispeed, cfsetospeed, tcsetattr):使用 tcgetattr() 获取当前串口配置,通过修改 tty 结构体中的各个标志位和属性,然后使用 tcsetattr() 设置新的串口属性。波特率设置为 B115200,表示通信速率为115200波特。
  • 数据发送 (write):使用 write() 函数向串口发送定义好的消息 MSG。
  • 数据接收 (read):使用 read() 函数从串口读取数据到 read_buf 中,读取的最大字节数为 sizeof(read_buf),并设置了超时时间。
  • 错误处理:在打开、写入和读取串口时,通过检查返回值和使用 errno 来处理错误情况,错误信息通过 strerror(errno) 获取并打印出来。
  • 清理和关闭:在程序的结尾使用 close(serial_fd) 关闭打开的串口设备文件。

运行结果:

3. Modbus编程

用于Modbus测试的几款软件:

VSPD 虚拟串口软件实现串口收发的功能

可参考博客VSPD虚拟串口软件安装及使用-CSDN博客

Modbus Poll :Modbus主机仿真器,用于测试和调试Modbus从设备。该软件支持ModbusRTU、ASCII、TCP/IP。用来帮助开发人员测试Modbus从设备,或者其它Modbus协议的测试和仿真。它支持多文档接口,即,可以同时监视多个从设备/数据域。每个窗口简单地设定从设备ID,功能,地址,大小和轮询间隔。你可以从任意一个窗口读写寄存器和线圈。如果你想改变一个单独的寄存器,简单地双击这个值即可。或者你可以改变多个寄存器/线圈值。提供数据的多种格式方式,比如浮点、双精度、长整型(可以字节序列交换)。

Modbus Slave: Modbus从设备仿真器,可以仿真32个从设备/地址域。每个接口都提供了对EXCEL报表的OLE自动化支持。主要用来模拟Modbus从站设备,接收主站的命令包,回送数据包。帮助Modbus通讯设备开发人员进行Modbus通讯协议的模拟和测试,用于模拟、测试、调试Modbus通讯设备。可以32个窗口中模拟多达32个Modbus子设备。与Modbus Poll的用户界面相同,支持功能01, 02, 03, 04, 05, 06, 15, 16, 22和23,监视串口数据。

可参考博客Modbus测试工具ModbusPoll与Modbus Slave使用方法_modbuspoll中文版-CSDN博客

i.MX6ULL的modbus做主机,实现功能码0x03,0x06,0x10

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

//CRC16校验
unsigned short calculateCRC(unsigned char *data, int length) {
    unsigned int crc = 0xFFFF;

    for (int i = 0; i < length; i++) {
        crc ^= (unsigned int)data[i];

        for (int j = 8; j != 0; j--) {
            if ((crc & 0x0001) != 0) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }

    return crc;
}

// 读取保持寄存器
void readHoldingRegisters(int fd, int address, int num) {
    unsigned char modbusFrame[] = {0x01, 0x03, 0x00, address, 0x00, num, 0x00, 0x00};  

    unsigned short crc = calculateCRC(modbusFrame, sizeof(modbusFrame) - 2);
    modbusFrame[sizeof(modbusFrame) - 2] = crc & 0xFF;
    modbusFrame[sizeof(modbusFrame) - 1] = (crc >> 8) & 0xFF;

    write(fd, modbusFrame, sizeof(modbusFrame));
/
    unsigned char buffer[256];
    int bytesRead = read(fd, buffer, sizeof(buffer));

    // 处理读取到的数据
    printf("Read Holding Registers Response: ");
    for (int i = 0; i < bytesRead; i++) {
        printf("%02X ", buffer[i]);
    }
    printf("\n");
}

// 写一个保持寄存器
void writeSingleRegister(int fd, int address, int value) {
    unsigned char modbusFrame[] = {0x01, 0x06, 0x00, address, (value >> 8) & 0xFF, value & 0xFF, 0x00, 0x00};

    unsigned short crc = calculateCRC(modbusFrame, sizeof(modbusFrame) - 2);
    modbusFrame[sizeof(modbusFrame) - 2] = crc & 0xFF;
    modbusFrame[sizeof(modbusFrame) - 1] = (crc >> 8) & 0xFF;

    write(fd, modbusFrame, sizeof(modbusFrame));
/
    unsigned char buffer[256];
    int bytesRead = read(fd, buffer, sizeof(buffer));

    // 处理读取到的数据
    printf("Read Holding Registers Response: ");
    for (int i = 0; i < bytesRead; i++) {
        printf("%02X ", buffer[i]);
    }
    printf("\n");

}

 // 写多个保持寄存器
void writeMultipleRegisters(int fd, int startAddress, int numRegisters, int *values) {
    unsigned char modbusFrame[256];
    modbusFrame[0] = 0x01;
    modbusFrame[1] = 0x10;
    modbusFrame[2] = 0x00;
    modbusFrame[3] = startAddress;
    modbusFrame[4] = 0x00;
    modbusFrame[5] = numRegisters;
    modbusFrame[6] = numRegisters * 2;

    for (int i = 0; i < numRegisters; i++) {
        modbusFrame[7 + i*2] = values[i] >> 8;
        modbusFrame[8 + i*2] = values[i] & 0xFF;
    }

    unsigned short crc = calculateCRC(modbusFrame, 7 + numRegisters * 2);
    modbusFrame[7 + numRegisters * 2] = crc & 0xFF;
    modbusFrame[8 + numRegisters * 2] = (crc >> 8) & 0xFF;

    write(fd, modbusFrame, 9 + numRegisters * 2);

/
    unsigned char buffer[256];
    int bytesRead = read(fd, buffer, sizeof(buffer));

    // 处理读取到的数据
    printf("Read Holding Registers Response: ");
    for (int i = 0; i < bytesRead; i++) {
        printf("%02X ", buffer[i]);
    }
    printf("\n");

}

int main() {
    int fd;
    struct termios tty;

    fd = open("/dev/ttymxc3", O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("Error opening serial port");
        return -1;
    }


	tcgetattr(fd, &tty);

	tty.c_cflag &= ~PARENB;
	tty.c_cflag &= ~CSTOPB;
	tty.c_cflag &= ~CSIZE;
	tty.c_cflag |= CS8;
	tty.c_cflag &= ~CRTSCTS;
	tty.c_cflag |= CREAD|CLOCAL;

	tty.c_lflag &= ~ICANON;
	tty.c_lflag &= ~ECHO;
	tty.c_lflag &= ~ECHOE;
	tty.c_lflag &= ~ECHONL;
	tty.c_lflag &= ~ISIG;

	tty.c_iflag &= ~(IXON | IXOFF | IXANY);
	tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);

	tty.c_oflag &= ~OPOST;
	tty.c_oflag &= ~ONLCR;

	tty.c_cc[VTIME] = 10;
	tty.c_cc[VMIN] = 0;

	cfsetispeed(&tty, B115200);
	cfsetospeed(&tty, B115200);

	tcsetattr(fd, TCSANOW, &tty);



    // 读取保持寄存器示例 ok
    readHoldingRegisters(fd, 0x0001, 0x0002);

    // 写单个寄存器示例  ok
    writeSingleRegister(fd, 0x0001, 0x1234);

    // 写多个寄存器示例 ok
    int values[] = {0xABCD, 0xEF01, 0x2345};
    writeMultipleRegisters(fd, 0x0004, 3, values);

    close(fd);

    return 0;
}

运行结果:

i.MX6ULL的modbus做从机,实现功能码0x03,0x06,0x10

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <stdint.h>
#include <string.h>

uint16_t registerValues[9] =
{
	0x0001,
	0x0002,
	0x0003,
	0x0004,
	0x0005,
	0x0006,
	0x0007,
	0x0008,
	0x0009,
};

// 计算CRC校验码
unsigned short calculateCRC(unsigned char *data, int length) {
    unsigned short crc = 0xFFFF;
    for (int i = 0; i < length; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

void handleModbusRequest(int fd) {

    unsigned char buffer[256];
	memset(buffer, 0, sizeof(buffer));
    int bytesRead = read(fd, buffer, sizeof(buffer));

    if (bytesRead < 0) {
        perror("Error reading from serial port");
        return;
    }
if (bytesRead > 0)
{
	for (int i=0; i<bytesRead; i++)
	{
		printf("0x%02x ", buffer[i]);
	}
	printf("\n");

}


    // 判断功能码
    if (buffer[1] == 0x03) {  // 读保持寄存器
        // 提取起始地址和寄存器数量
printf("03\n");
        int startAddress = (buffer[2] << 8) | buffer[3];
        int numRegisters = (buffer[4] << 8) | buffer[5];
        
        // 从数据库或传感器中读取对应寄存器的值
        //int registerValues[numRegisters];
        // 读取寄存器的值,并填充到 registerValues 数组中
        
        // 构建响应报文
        unsigned char response[5 + numRegisters * 2];
        response[0] = buffer[0];  // 从机地址
        response[1] = buffer[1];  // 功能码
        response[2] = numRegisters * 2;  // 字节数
        for (int i = 0; i < numRegisters; i++) {
            response[3 + i*2] = (registerValues[i] >> 8) & 0xFF;
            response[4 + i*2] = registerValues[i] & 0xFF;
        }
        
        // 计算并添加 CRC
        unsigned short crc = calculateCRC(response, 3 + numRegisters * 2);
        response[3 + numRegisters * 2] = crc & 0xFF;
        response[4 + numRegisters * 2] = (crc >> 8) & 0xFF;
        
        // 发送响应报文
        write(fd, response, 5 + numRegisters * 2);
    } else if (buffer[1] == 0x06) {  // 写单个寄存器
printf("06\n"); 
		// 提取寄存器地址和值
        int address = (buffer[2] << 8) | buffer[3];
        int value = (buffer[4] << 8) | buffer[5];
        
        // 将 value 写入到对应地址的寄存器中
		registerValues[address] = value;
		for (int i=0; i<9; i++)
		{
			printf("0x%04x ", registerValues[i]);
		}
		printf("\n");
        
        // 构建并发送响应报文
        unsigned char response[] = {buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], 0x00, 0x00};  // 与请求报文一致
        unsigned short crc = calculateCRC(response, sizeof(response) - 2);
        response[sizeof(response) - 2] = crc & 0xFF;
        response[sizeof(response) - 1] = (crc >> 8) & 0xFF;
        write(fd, response, sizeof(response));
    } else if (buffer[1] == 0x10) {  // 写多个寄存器
printf("10\n");
		// 提取起始地址、寄存器数量和字节数
        int startAddress = (buffer[2] << 8) | buffer[3];
        int numRegisters = (buffer[4] << 8) | buffer[5];
        int byteCount = buffer[6];
        
        // 提取写入的寄存器值
        int values[numRegisters];
        for (int i = 0; i < numRegisters; i++) {
            values[i] = (buffer[7 + i*2] << 8) | buffer[8 + i*2];
        }
        
        // 将 values 数组中的值写入到对应地址的寄存器中
        if (startAddress + numRegisters <= sizeof(registerValues) / sizeof(registerValues[0])) {
        for (int i = 0; i < numRegisters; i++) {
            registerValues[startAddress + i] = values[i];
        }

		for (int i=0; i<9; i++)
		{
			printf("0x%04x ", registerValues[i]);
		}
		printf("\n");

        // 构建并发送响应报文
        unsigned char response[] = {buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], 0x00, 0x00};  // 与请求报文一致
        unsigned short crc = calculateCRC(response, sizeof(response) - 2);
        response[sizeof(response) - 2] = crc & 0xFF;
        response[sizeof(response) - 1] = (crc >> 8) & 0xFF;
        write(fd, response, sizeof(response));
    } else {
        // 未知功能码,可以进行相应的处理
    }
}
}

int main() {
    int fd;
    struct termios tty;

    fd = open("/dev/ttymxc3", O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("Error opening serial port");
        return -1;
    }


	tcgetattr(fd, &tty);

	tty.c_cflag &= ~PARENB;
	tty.c_cflag &= ~CSTOPB;
	tty.c_cflag &= ~CSIZE;
	tty.c_cflag |= CS8;
	tty.c_cflag &= ~CRTSCTS;
	tty.c_cflag |= CREAD|CLOCAL;

	tty.c_lflag &= ~ICANON;
	tty.c_lflag &= ~ECHO;
	tty.c_lflag &= ~ECHOE;
	tty.c_lflag &= ~ECHONL;
	tty.c_lflag &= ~ISIG;

	tty.c_iflag &= ~(IXON | IXOFF | IXANY);
	tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);

	tty.c_oflag &= ~OPOST;
	tty.c_oflag &= ~ONLCR;

	tty.c_cc[VTIME] = 30;
	tty.c_cc[VMIN] = 0;

	cfsetispeed(&tty, B115200);
	cfsetospeed(&tty, B115200);

	tcsetattr(fd, TCSANOW, &tty);

    while (1) {
        handleModbusRequest(fd);
    }

    close(fd);

    return 0;
}

运行结果:

4. LibModbus库编程实现

对LibModbus库的下载和使用可以参考以下链接

Modbus驱动库—libmodbus驱动库的使用-CSDN博客

libmodbus 开源库(第一部分)-CSDN博客

libmodbus库rtu主机和从机通信

  • 主机代码:
#include <stdio.h>
#include <errno.h>
#include <modbus.h>

int main() {
    modbus_t *ctx;
    uint16_t tab_reg[32]; // 用于存储从机返回的寄存器值
    int rc;

    ctx = modbus_new_rtu("/dev/ttymxc3", 115200, 'N', 8, 1);
    if (ctx == NULL) {
        fprintf(stderr, "Unable to create the libmodbus context\n");
        return -1;
    }

    modbus_set_slave(ctx, 1); // 设置从机地址

    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 读保持寄存器
    rc = modbus_read_registers(ctx, 0, 10, tab_reg);
    if (rc == -1) {
        fprintf(stderr, "Read error: %s\n", modbus_strerror(errno));
    } else {
        int i;
        printf("Read(0x03) successful. Data:");
        for (i = 0; i < rc; i++) {
            printf(" %d", tab_reg[i]);
        }
        printf("\n");
    }

    // 写单个寄存器
    uint16_t reg_value = 1234;
    rc = modbus_write_register(ctx, 0, reg_value);
    if (rc == -1) {
        fprintf(stderr, "Write error: %s\n", modbus_strerror(errno));
    } else {
        printf("Write(0x06) successful.\n");
    }

    // 读保持寄存器
    rc = modbus_read_registers(ctx, 0, 10, tab_reg);
    if (rc == -1) {
        fprintf(stderr, "Read error: %s\n", modbus_strerror(errno));
    } else {
        int i;
        printf("Read(0x06) successful. Data:");
        for (i = 0; i < rc; i++) {
            printf(" %d", tab_reg[i]);
        }
        printf("\n");
    }




    // 写多个寄存器
    uint16_t tab_reg_values[5] = {1, 2, 3, 4, 5};
    rc = modbus_write_registers(ctx, 0, 5, tab_reg_values);
    if (rc == -1) {
        fprintf(stderr, "Write error: %s\n", modbus_strerror(errno));
    } else {
        printf("Write(0x10) successful.\n");
    }

    // 读保持寄存器
    rc = modbus_read_registers(ctx, 0, 10, tab_reg);
    if (rc == -1) {
        fprintf(stderr, "Read error: %s\n", modbus_strerror(errno));
    } else {
        int i;
        printf("Read(0x03) successful. Data:");
        for (i = 0; i < rc; i++) {
            printf(" %d", tab_reg[i]);
        }
        printf("\n");
    }

    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}
  • 从机代码:
#include <stdio.h>
#include <errno.h>
#include <modbus.h>



int main() {
    modbus_t *ctx;
    int s;
    
    ctx = modbus_new_rtu("/dev/ttymxc6", 115200, 'N', 8, 1);
    if (ctx == NULL) {
        fprintf(stderr, "Unable to create the libmodbus context\n");
        return -1;
    }

    modbus_set_slave(ctx, 1); // 设置从机地址

    s = modbus_connect(ctx);

    if (s == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

	modbus_mapping_t *mb_mapping = modbus_mapping_new(500, 500, 500, 500);
	if (mb_mapping == NULL) {
		fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno));
		modbus_free(ctx);
        return -1;
	}


    while (1) {
        uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
        int rc;

        rc = modbus_receive(ctx, query);
        if (rc > 0) {

            printf("len=%02d: ", rc);
            for(int idx = 0; idx < rc; idx++)
            {
                printf(" %02x", query[idx]);
            }
            printf("\n");

            // 处理主机的请求,这里演示简单地回复寄存器值
            modbus_reply(ctx, query, rc, mb_mapping); // 修正了函数参数

            
        } else if (rc == -1) {
            fprintf(stderr, "Error in modbus_receive: %s\n", modbus_strerror(errno));
            break;
        }
    }

	modbus_mapping_free(mb_mapping);
    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}

运行结果:

libmodbus库tcp服务端和客户端通信

  • 服务端代码:
#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <errno.h>
#include <stdlib.h>

#include <modbus.h>

int main(void)
{
    int s = -1;
    modbus_t *ctx;
    modbus_mapping_t *mb_mapping;

    ctx = modbus_new_tcp("127.0.0.1", 1502);
    /* modbus_set_debug(ctx, TRUE); */

    mb_mapping = modbus_mapping_new(500, 500, 500, 500);
    if (mb_mapping == NULL) {
        fprintf(stderr, "Failed to allocate the mapping: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    s = modbus_tcp_listen(ctx, 1);
    modbus_tcp_accept(ctx, &s);

    for (;;) {
        uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
        int rc;

        rc = modbus_receive(ctx, query);
        if (rc > 0) {
            /* rc is the query size */
            
            printf("len=%02d: ", rc);
            for(int idx = 0; idx < rc; idx++)
            {
              printf(" %02x", query[idx]);
            }
            printf("\n");
            
            modbus_reply(ctx, query, rc, mb_mapping);
        } else if (rc == -1) {
            /* Connection closed by the client or error */
            break;
        }
    }

    printf("Quit the loop: %s\n", modbus_strerror(errno));

    if (s != -1) {
        close(s);
    }
    modbus_mapping_free(mb_mapping);
    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}
  • 客户端代码:
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <modbus.h>

int main(void)
{
    modbus_t *ctx;
    int rc;
    uint16_t tab_reg[32];

    ctx = modbus_new_tcp("127.0.0.1", 1502);
    if (ctx == NULL) {
        fprintf(stderr, "Unable to allocate libmodbus context\n");
        return -1;
    }

    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    // 读保持寄存器
    rc = modbus_read_registers(ctx, 0, 10, tab_reg);
    if (rc == -1) {
        fprintf(stderr, "Read error: %s\n", modbus_strerror(errno));
    } else {
        int i;
        printf("Read(0x03) successful. Data:");
        for (i = 0; i < rc; i++) {
            printf(" %d", tab_reg[i]);
        }
        printf("\n");
    }

    // 写单个寄存器
    uint16_t reg_value = 1234;
    rc = modbus_write_register(ctx, 0, reg_value);
    if (rc == -1) {
        fprintf(stderr, "Write error: %s\n", modbus_strerror(errno));
    } else {
        printf("Write(0x06) successful.\n");
    }

    // 读保持寄存器
    rc = modbus_read_registers(ctx, 0, 10, tab_reg);
    if (rc == -1) {
        fprintf(stderr, "Read error: %s\n", modbus_strerror(errno));
    } else {
        int i;
        printf("Read(0x06) successful. Data:");
        for (i = 0; i < rc; i++) {
            printf(" %d", tab_reg[i]);
        }
        printf("\n");
    }




    // 写多个寄存器
    uint16_t tab_reg_values[5] = {1, 2, 3, 4, 5};
    rc = modbus_write_registers(ctx, 0, 5, tab_reg_values);
    if (rc == -1) {
        fprintf(stderr, "Write error: %s\n", modbus_strerror(errno));
    } else {
        printf("Write(0x10) successful.\n");
    }

    // 读保持寄存器
    rc = modbus_read_registers(ctx, 0, 10, tab_reg);
    if (rc == -1) {
        fprintf(stderr, "Read error: %s\n", modbus_strerror(errno));
    } else {
        int i;
        printf("Read(0x03) successful. Data:");
        for (i = 0; i < rc; i++) {
            printf(" %d", tab_reg[i]);
        }
        printf("\n");
    }

    modbus_close(ctx);
    modbus_free(ctx);

    return 0;
}

运行结果:

三、STM32下的RS485开发

1. 单片机下串口编程

STM32CubeMx配置

1. 首先在STM32CubeMx选择自己单片机的芯片生成工程

2. 开启外部时钟

3. 选择Serial Wire,避免烧录调试时可能让单片机变成砖的问题

4. 使能并配置UART1

STM32CubeIDE编写代码

1. 在usart.h文件添加

  • 添加头文件声明

2. 在usart.c文件添加

  • 添加串口重定向

3. 在main.c文件里的while(1)循环前添加printf打印

4. 把程序烧录到单片机,串口调试助手打印信息

2. Modbus编程

STM32CubeMx配置

1. 查看单片机的RS485引脚原理图,用STM32CubeMx配置生成代码

 

 

 STM32CubeIDE编写代码

1. 在usart.h文件添加

  • 宏定义RS485发送和接收,声明lpuart1接收数据的buf等

2. 在usart.c文件添加

  • 定义lpuart1中断接收的buf

  • 在lpuart1初始化中添加使能接收中断

  • 添加串口中断回调函数

3. 新建文件my_modbus.h

#ifndef INC_MODBUS_H_
#define INC_MODBUS_H_

#include <stdio.h>
#include <stm32l4xx_hal.h>

#include <usart.h>



 typedef struct
 {
 	//作为从机时使用
  	uint8_t  myadd;        //本设备从机地址
  	uint8_t  rcbuf[100];   //modbus接受缓冲区
 	uint8_t  timout;       //modbus数据持续时间
 	uint8_t  recount;      //modbus端口接收到的数据个数
 	uint8_t  timrun;       //modbus定时器是否计时标志
 	uint8_t  reflag;       //modbus一帧数据接受完成标志位
 	uint8_t  sendbuf[100]; //modbus接发送缓冲区

 	//作为主机添加部分
 	uint8_t Host_Txbuf[8];	//modbus发送数组
 	uint8_t slave_add;		//要匹配的从机设备地址(做主机实验时使用)
 	uint8_t Host_send_flag;//主机设备发送数据完毕标志位
 	int 	Host_Sendtime;//发送完一帧数据后时间计数
 	uint8_t Host_time_flag;//发送时间到标志位,=1表示到发送数据时间了
 	uint8_t Host_End;//接收数据后处理完毕
 }MODBUS;

extern uint16_t Reg[];
extern MODBUS modbus;

extern void Modbus_Init();
extern void Host_Read03_slave(uint8_t slave,uint16_t StartAddr,uint16_t num);
extern void Host_write06_slave(uint8_t slave,uint16_t StartAddr,uint16_t num);
extern void Host_write16_slave(uint8_t slave,uint16_t StartAddr,int numRegisters, int *values);

#endif /* INC_MODBUS_H_ */

4. 新建文件my_modbus.c

#include "my_modbus.h"

uint16_t Reg[] ={0x0001,
            0x0012,
            0x0013,
            0x0004,
	    0x0025,
            0x0036,
            0x0007,
	    0X0008,
           };//reg是提前定义好的寄存器和寄存器数据,要读取和改写的部分内容

MODBUS modbus;

 // Modbus初始化函数
 void Modbus_Init()
 {

        modbus.myadd = 0x02; //从机设备地址为2
   	modbus.timrun = 0;    //modbus定时器停止计算
 	modbus.slave_add=0x01;//主机要匹配的从机地址(本设备作为主机时)
 }

uint16_t Modbus_CRC16(unsigned char *data, int length) {
     unsigned short crc = 0xFFFF;
     for (int i = 0; i < length; i++) {
         crc ^= data[i];
         for (int j = 0; j < 8; j++) {
             if (crc & 0x0001) {
                 crc >>= 1;
                 crc ^= 0xA001;
             } else {
                 crc >>= 1;
             }
         }
     }
     return crc;
 }

 //参数1从机地址,参数2起始地址,参数3寄存器个数
 void Host_Read03_slave(uint8_t slave,uint16_t StartAddr,uint16_t num)
 {
 	//int j;
 	uint16_t crc;//计算的CRC校验位
 	modbus.slave_add=slave;//这是先把从机地址存储下来,后面接收数据处理时会用到
 	modbus.Host_Txbuf[0]=slave;//这是要匹配的从机地址
 	modbus.Host_Txbuf[1]=0x03;//功能码
 	modbus.Host_Txbuf[2]=StartAddr/256;//起始地址高位
 	modbus.Host_Txbuf[3]=StartAddr%256;//起始地址低位
 	modbus.Host_Txbuf[4]=num/256;//寄存器个数高位
 	modbus.Host_Txbuf[5]=num%256;//寄存器个数低位
 	crc=Modbus_CRC16(&modbus.Host_Txbuf[0],6); //获取CRC校验位
 	modbus.Host_Txbuf[6]=crc%256;//寄存器个数高位                        这里错了,要先发crc低位再到crc高位
 	modbus.Host_Txbuf[7]=crc/256;//寄存器个数低位
 	//发送数据包装完毕(共8个字节)
 	//开始发送数据
 	RS485_TX_ENABLE;//使能485控制端(启动发送)
 	HAL_UART_Transmit(&hlpuart1, modbus.Host_Txbuf, 8, 0xFFFF);
 	//for(j=0;j<8;j++)
 	//{
 	 //Modbus_Send_Byte(modbus.sendbuf[j]);
 	//}
 	RS485_RX_ENABLE;//失能485控制端(改为接收)
 }

 void Host_Func3()
 {
 	int i;
 	int count=(int)g_lpuart1_rxbuf[2];//这是数据个数

 	printf("从机返回 %d 个寄存器数据:\r\n",count/2);
 	for(i=0;i<count;i=i+2)
 	{
 		printf("Temp_Hbit= %d Temp_Lbit= %d temp= %d\r\n",(int)g_lpuart1_rxbuf[3+i],(int)g_lpuart1_rxbuf[4+i],(int)g_lpuart1_rxbuf[4+i]+((int)g_lpuart1_rxbuf[3+i])*256);
 	}
 }

 //主机接收从机的消息进行处理功能码0x03
 void HOST_ModbusRX()
 {
// 	uint16_t crc,rccrc;//计算crc和接收到的crc
//
//   if(modbus.reflag == 0)  //如果接收未完成则返回空
// 	{
// 	   return;
// 	}
// 	//接收数据结束

// 	//(数组中除了最后两位CRC校验位其余全算)
// 	crc = Modbus_CRC16(&modbus.rcbuf[0],modbus.recount-2); //获取CRC校验位
// 	rccrc = modbus.rcbuf[modbus.recount-2]*256+modbus.rcbuf[modbus.recount-1];//计算读取的CRC校验位
//
// 	if(crc == rccrc) //CRC检验成功 开始分析包
// 	{
// 	   if(modbus.rcbuf[0] == modbus.slave_add)  // 检查地址是是对应从机发过来的
// 		 {
// 			 if(modbus.rcbuf[1]==3)//功能码时03
// 		      Host_Func3();//这是读取寄存器的有效数据位进行计算
// 		 }
//
// 	}

// 	crc = Modbus_CRC16((uint8_t *)&g_lpuart1_rxbuf[0], g_lpuart1_bytes-2); //获取CRC校验位
// 	rccrc = g_lpuart1_rxbuf[g_lpuart1_bytes-2]*256+g_lpuart1_rxbuf[g_lpuart1_bytes-1];//计算读取的CRC校验位
//
// 	if(crc == rccrc) //CRC检验成功 开始分析包
// 	{
 	   if(g_lpuart1_rxbuf[0] == modbus.slave_add)  // 检查地址是是对应从机发过来的
 		 {
 			 if(g_lpuart1_rxbuf[1]==3)//功能码时03
 		      Host_Func3();//这是读取寄存器的有效数据位进行计算
 		 }

// 	}

// 	 modbus.recount = 0;//接收计数清零
//    modbus.reflag = 0; //接收标志清零

 }



 //向一个寄存器中写数据的参数设置
 void Host_write06_slave(uint8_t slave,uint16_t StartAddr,uint16_t num)
 {
 	uint16_t crc,j;//计算的CRC校验位
 	modbus.slave_add=slave;//从机地址赋值一下,后期有用
 	modbus.Host_Txbuf[0]=slave;//这是要匹配的从机地址
 	modbus.Host_Txbuf[1]=0x06;//功能码
 	modbus.Host_Txbuf[2]=StartAddr/256;//起始地址高位
 	modbus.Host_Txbuf[3]=StartAddr%256;//起始地址低位
 	modbus.Host_Txbuf[4]=num/256;
 	modbus.Host_Txbuf[5]=num%256;
 	crc=Modbus_CRC16(&modbus.Host_Txbuf[0],6); //获取CRC校验位
 	modbus.Host_Txbuf[6]=crc%256;//寄存器个数高位                      这里错了,要先发crc低位再到crc高位
 	modbus.Host_Txbuf[7]=crc/256;//寄存器个数低位
 	//发送数据包装完毕
 	//开始发送数据
 	RS485_TX_ENABLE;//使能485控制端(启动发送)
 	HAL_UART_Transmit(&hlpuart1, modbus.Host_Txbuf, 8, 0xFFFF);
// 	for(j=0;j<8;j++)
// 	{
// 	 Modbus_Send_Byte(modbus.sendbuf[j]);
// 	}
 	RS485_RX_ENABLE;//失能485控制端(改为接收)
 }

 void Host_write16_slave(uint8_t slave,uint16_t StartAddr,int numRegisters, int *values)
 {
 	uint16_t crc,j;//计算的CRC校验位
 	modbus.slave_add=slave;//从机地址赋值一下,后期有用
 	modbus.Host_Txbuf[0]=slave;//这是要匹配的从机地址
 	modbus.Host_Txbuf[1]=0x10;//功能码
 	modbus.Host_Txbuf[2]=StartAddr/256;//起始地址高位
 	modbus.Host_Txbuf[3]=StartAddr%256;//起始地址低位
 	modbus.Host_Txbuf[4]=numRegisters/256;
 	modbus.Host_Txbuf[5]=numRegisters%256;
 	modbus.Host_Txbuf[6]=numRegisters * 2;

    for (int i = 0; i < numRegisters; i++) {
    	modbus.Host_Txbuf[7 + i*2] = values[i] >> 8;
    	modbus.Host_Txbuf[8 + i*2] = values[i] & 0xFF;
    }

 	crc=Modbus_CRC16(&modbus.Host_Txbuf[0],7 + numRegisters * 2); //获取CRC校验位
// 	modbus.Host_Txbuf[7]=crc%256;//寄存器个数高位                      这里错了,要先发crc低位再到crc高位
// 	modbus.Host_Txbuf[8]=crc/256;//寄存器个数低位
 	modbus.Host_Txbuf[7 + numRegisters * 2] = crc & 0xFF;
 	modbus.Host_Txbuf[8 + numRegisters * 2] = (crc >> 8) & 0xFF;
 	//发送数据包装完毕
 	//开始发送数据
 	RS485_TX_ENABLE;//使能485控制端(启动发送)
 	HAL_UART_Transmit(&hlpuart1, modbus.Host_Txbuf, 9 + numRegisters * 2, 0xFFFF);
// 	for(j=0;j<8;j++)
// 	{
// 	 Modbus_Send_Byte(modbus.sendbuf[j]);
// 	}
 	RS485_RX_ENABLE;//失能485控制端(改为接收)
 }

5. 在main.c添加打印lpuart1的buf数据

6. 在while(1)循环添加代码,STM32做主机的modbus,实现功能码0x03,0x06,0x10

3. FreeModbus库编程实现

移植FreeModbus库

1. 以下是我们所需要的文件

2. 开启TIM2

3. 主要修改port文件夹里的porttimer.c和portserial.c文件,配置我们的TIM和UART

  • porttimer.c
/*
 * FreeModbus Libary: RT-Thread Port
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id: porttimer.c,v 1.60 2013/08/13 15:07:05 Armink $
 */

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "tim.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"



//extern TIM_HandleTypeDef htim2;


/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR(void);

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{
	MX_TIM2_Init();
    return TRUE;
}

void vMBPortTimersEnable()
{
    // 开启定时器,计算是否超时
	__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);  // 清除中断标志位
    __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE); //开启定时器自动更新中断
	__HAL_TIM_SET_COUNTER(&htim2, 0x0000);  // 设置计数器的值为0
	__HAL_TIM_ENABLE(&htim2);   // 开启定时器中断
}

void vMBPortTimersDisable()
{   
	__HAL_TIM_DISABLE(&htim2);    // 关闭定时器中断
	__HAL_TIM_SET_COUNTER(&htim2, 0x0000);  // 设置计数器的值为0
	__HAL_TIM_DISABLE_IT(&htim2, TIM_IT_UPDATE); //关闭定时器自动更新中断
	__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);  // 清除中断标志位
}

void prvvTIMERExpiredISR(void)
{
    (void)pxMBPortCBTimerExpired();
}



//void TIM2_IRQHandler(void)
//{
//
//  HAL_TIM_IRQHandler(&htim2);
//  if(__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET)
//  {
//    prvvTIMERExpiredISR();
//  	__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);  // 清除中断标志位
//  }
//  /* USER CODE BEGIN TIM2_IRQn 1 */
//
//  /* USER CODE END TIM2_IRQn 1 */
//}
  • portserial.c
/*
 * FreeModbus Libary: RT-Thread Port
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id: portserial.c,v 1.60 2013/08/13 15:07:05 Armink $
 */

#include "port.h"

#include "usart.h"
#include "stm32l4xx_hal.h"
#include "stm32l4xx_hal_uart.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR(void);
void prvvUARTRxISR(void);



//extern UART_HandleTypeDef huart1;


/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
                       eMBParity eParity)
{
	MX_LPUART1_UART_Init();
    return TRUE;
}

void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
   ENTER_CRITICAL_SECTION();
		  
  if (xRxEnable)
  {
  	  //使能串口接收中断
	  RS485_RX_ENABLE;
	 __HAL_UART_ENABLE_IT(&hlpuart1, UART_IT_RXNE);

  }
  else
  {
     //关闭串口接收中断
	 __HAL_UART_DISABLE_IT(&hlpuart1, UART_IT_RXNE);
  }


  if (xTxEnable)
  {
     //使能串口发送中断
	  RS485_TX_ENABLE;
	 __HAL_UART_ENABLE_IT(&hlpuart1, UART_IT_TXE);

  }
  else
  {
 	 //关闭串口发送中断
	 __HAL_UART_DISABLE_IT(&hlpuart1, UART_IT_TXE);
  }
	  
  EXIT_CRITICAL_SECTION();
}

void vMBPortClose(void)
{
	//不做函数处理
}

BOOL xMBPortSerialPutByte(CHAR ucByte)
{ 
	// 发送串口数据 一次发送一个数据
	HAL_UART_Transmit(&hlpuart1, &ucByte,1,5);
    return TRUE;
}

BOOL xMBPortSerialGetByte(CHAR *pucByte)
{
	// 获取串口数据 一次拿一个数据
	HAL_UART_Receive(&hlpuart1,pucByte,1, 0);
    return TRUE;
}

/*
 * Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call
 * xMBPortSerialPutByte( ) to send the character.
 */
void prvvUARTTxReadyISR(void)
{
    pxMBFrameCBTransmitterEmpty();
}

/*
 * Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
void prvvUARTRxISR(void)
{
    pxMBFrameCBByteReceived();

}

//void USART1_IRQHandler(void)
//{
//
//  HAL_UART_IRQHandler(&huart1);
//  if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
//  {    //获取接收RXNE标志位是否被置位
//	 prvvUARTRxISR();
//	 __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
//
//  }
//
//  if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE) == SET)
//  {
//	prvvUARTTxReadyISR();
//	__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);
//  }
//
//
//}

4. 配置stm32lxxx_it.c文件里面的IRQHAND

5. 在main.c添加

  • 在while(1)循环前初始化寄存器,波特率,使能LibModbus通信

  • 在while(1)循环里添加从机响应代码,并打印寄存器的值

6. 烧录到单片机验证

四、STM32与i.MX6ULL的RS485通信(Modbus)

根据上面所写的例子,我们就可以稍微修改代码实现STM32与i.MX6ULL的RS485通信

下面直接给出2个板子通信成功的结果

  • i.MX6ULL做主机,STM32做从机的modbus收发

  • i.MX6ULL的libmodbus和STM32的freemodbus互相通信

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值