目录
i.MX6ULL的modbus做主机,实现功能码0x03,0x06,0x10
i.MX6ULL的modbus做从机,实现功能码0x03,0x06,0x10
四、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协议特点
- 抗干扰能力强:RS485接口采用差分信号传输方式,可以有效地抵抗电磁干扰和噪声干扰,确保数据传输的稳定性和可靠性。
- 传输距离远:RS485接口的最大传输距离可达1200米(在特定条件下),这使得它在需要远距离数据传输的应用中具有独特的优势。
- 传输速率高:RS485接口的传输速率在短距离内可达35Mbps,即使在1200米的距离下,传输速率仍可达100Kbps或更高(具体取决于波特率和电缆质量)。这种高速传输特性使得RS485能够满足各种实时性要求较高的应用需求。
- 支持节点多:RS485接口一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,甚至最大可以支持到400个节点。这种多点通信特性使得RS485能够满足各种复杂网络的需求。
- 接口电平低:RS485的电气特性使得其接口电路不易损坏,同时也方便与TTL电路连接。RS485采用负逻辑,逻辑“0”以两线间的电压差为+(2~6)V表示,逻辑“1”以两线间的电压差为-(2~6)表示。
- 半双工通信:RS485接口支持半双工串行通信模式,即在同一时间内只能进行数据发送或接收操作。虽然这在一定程度上降低了数据传输的效率,但简化了电路设计并降低了成本。
- 总线结构:RS485接口采用总线结构进行数据传输,可以方便地实现多台设备之间的数据通信和共享。
3. Modbus协议介绍
Modbus协议是一种广泛应用于工业自动化系统中的通信协议,它最初由Modicon公司(现为施耐德电气Schneider Electric)于1979年发布。
协议架构
- 主从结构:网络中有一个主设备向多个从设备发起请求,从设备响应请求
- Modbus协议分为应用层、数据链路层和物理层。
- 应用层:定义了Modbus消息的格式和内容,包含功能码、数据地址、数据长度以及数据内容等信息;
- 数据链路层:负责传输Modbus消息,包括帧起始符、帧结束符、校验位等;
- 物理层:定义了消息的物理传输方式,如电气特性、传输速率等。Modbus协议在物理层上支持多种通信方式,如RS-232、RS-422和RS-485等。
传输模式
Modbus协议支持多种传输模式,包括RTU(远程终端单元)模式、ASCII模式和TCP/IP(如以太网)模式。
- RTU模式:采用二进制表示数据,速度快且可靠;包含设备地址、功能码、数据区(长度根据功能码决定)、CRC校验码;比ASCII模式具有更高的数据传输速率和效率。
- ASCII模式:采用ASCII码格式,在RTU模式基础上增加了起始字符、结束字符、和LRC校验码。
- 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类型
- 读保存寄存器功能码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校验码 |
- 写单个保存寄存器功能码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校验码 |
- 写多个保存寄存器功能码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)等。
- 无(None):不发送校验位。
- 奇校验(Odd):如果数据位中1的数量是偶数,则校验位为1,使得总位数(包括校验位)中1的数量为奇数。
- 偶校验(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 虚拟串口软件实现串口收发的功能
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库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库
- 需要移植的FreeModbus源码:GitHub - armink/FreeModbus_Slave-Master-RTT-STM32: Add master mode to FreeModbus. | 在 FreeModbus 中添加主机模式
- 可参考链接移植:STM32 HAL库实现FreeRTOS+FreeModbus(从机和主机)_freemodbus freertos-CSDN博客
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互相通信