目录
一、基础信息
1.1、适用场景
C程序Modbus-RTU模式,主机读取从机线圈状态时,从机生成响应数据的处理程序。
响应数据参照GB-T19582.1-2008《基于Modbus协议的工业自动化网络规范 第1部分:Modbus应用协议》。
1.2、变量类型
uint8_t,为8位无符号整形变量;
uint16_t,为16位无符号整形变量。
1.3、实现功能
使用十六进制(即0x0000~0xFFFF)表示线圈地址。地址高字节相等的线圈为同一扇区,如扇区1的线圈地址域为0x0000~0x00FF,扇区2的线圈地址域为0x0100~0x01FF。
本例程只列举扇区1和扇区2,其中扇区1共4个寄存器,扇区2共3个寄存器。
1.4、调用非本文函数
CRC16_Modbus参照:Modbus查表法CRC校验程序_VIFIN的博客-CSDN博客C程序编写Modbus查表法的CRC校验。https://blog.csdn.net/VIFIN/article/details/125788125
ModbusError参照:
二、主要程序内容
2.1、头文件声明内容
头文件主要声明线圈扇区,以及各扇区的寄存器功能。并定义一个用于存放所有线圈的二维数组Coils,二维数组的行数量为线圈扇区数量,二维数组的列数量是线圈数量最多的扇区的线圈数。
enum CoilsSection //线圈扇区
{
CoilsSection_01, //线圈扇区1
CoilsSection_02, //线圈扇区2
//根据需要扩展新的扇区
CoilsSection_Number //线圈扇区数量
};
enum DefineCoilsSection_01 //定义线圈扇区1
{
Coil_01, //实际使用填写线圈名称
Coil_02, //实际使用填写线圈名称
Coil_03, //实际使用填写线圈名称
Coil_04, //实际使用填写线圈名称
//根据需要填写新的线圈名称
CoilsSection_01_Number //线圈扇区1数量
};
enum DefineCoilsSection_02 //定义线圈扇区1
{
Coil_05, //实际使用填写线圈名称
Coil_06, //实际使用填写线圈名称
Coil_07, //实际使用填写线圈名称
//根据需要填写新的线圈名称
CoilsSection_02_Number //线圈扇区1数量
};
#define MaxCoilsSectionSize CoilsSection_01_Number //根据实际情况,定义线圈数量最多的扇区的线圈数量
extern uint16_t Coils[CoilsSection_Number][MaxCoilsSectionSize];
2.2、工程文件定义内容
工程文件只需要定义已经声明的二维数组Coils。
uint16_t Coils[CoilsSection_Number][MaxCoilsSectionSize];
2.3、子函数
子函数ReadCoilsCreateString,主要功能是生成读取线圈指令的响应数据。使用之前,需要排除所有错误情况。
/*
* 功能:生成读取线圈成功的返回数据
* 输入: @p_read_data_string:读取数据的首指针地址
* @p_retrun_data_string:返回数据的首指针地址
* @p_return_data_size:返回字节长度的存放指针地址
* @coils_section:线圈扇区
* @coils_number:线圈数量
* 输出:Modbus函数执行结果
*/
ModbusResult_TypeDef ReadCoilsCreateString(
uint8_t* p_read_data_string,
uint8_t* p_retrun_data_string,
uint8_t* p_return_data_size,
uint8_t coils_section,
uint8_t coils_number
)
{
uint8_t i,h;
uint8_t OutputByte = 0;
uint16_t ReadCoilsNumber = 0;
uint16_t crc_buffer = 0;
ReadCoilsNumber = *(p_read_data_string + 4) * 256 + *(p_read_data_string + 5); //计算:读取线圈长度
OutputByte = (ReadCoilsNumber - 1) / 8 + 1; //计算:返回数据字节数
*(p_retrun_data_string + 0) = *(p_read_data_string + 0); //返回缓存赋值:从机地址
*(p_retrun_data_string + 1) = *(p_read_data_string + 1); //返回缓存赋值:功能码
*(p_retrun_data_string + 2) = OutputByte; //返回缓存赋值:字节数
ReadCoilsNumber = *(p_read_data_string + 3); //赋值:读取线圈首地址
for(h = 0;h < OutputByte;h ++)
{
for(i = 0;i < 8;i ++)
{
if(ReadCoilsNumber < coils_number) //判定:读取线圈有效
{
if(Coils[coils_section][ReadCoilsNumber] == 0xFF00) //判定:线圈为ON
{
*(p_retrun_data_string + 3 + h) |= 0x80;
}
}
*(p_retrun_data_string + 3 + h) >>= 1; //左移一位
ReadCoilsNumber ++; //累加:读取线圈数量
}
}
crc_buffer = CRC16_Modbus(3 + h, p_retrun_data_string); //计算CRC
*(p_retrun_data_string + 3 + h) = crc_buffer % 256;
*(p_retrun_data_string + 4 + h) = crc_buffer / 256;
*p_return_data_size = 5 + h; //赋值:返回数据长度
return Modbus_OK; //返回:Modbus执行成功
}
p_read_data_string:读取数据的首指针地址,一般填写串口接收缓存数组的数组名;
p_retrun_data_string:返回数据的首指针地址,一般填写串口发送缓存数组的数组名;
p_return_data_size:返回字节长度的存放指针地址,一般填写发送长度缓存变量的指针地址;
coils_section:线圈扇区,直接填写所要读取的线圈扇区,填写内容为声明枚举“ CoilsSection”的声明内容;
coils_number:线圈数量,填写所要读取线圈扇区的线圈数量,填写内容为声明枚举“DefineCoilsSection_xx”的最后一个枚举元素名。
子函数ReadCoils,用于判断读取的线圈首地址和线圈数量是否符合要求。若不符合要求,则调用子函数“ModbusError”生成错误响应数据;若符合要求,则调用“ReadCoilsCreateString”生成正常响应数据。
/*
* 功能:读取线圈的值
* 输入: @p_read_data_string:读取数据的首指针地址
* @p_retrun_data_string:返回数据的首指针地址
* @p_return_data_size:返回字节长度的存放指针地址
* 输出:Modbus函数执行结果
*/
ModbusResult_TypeDef ReadCoils(
uint8_t* p_read_data_string,
uint8_t* p_retrun_data_string,
uint8_t* p_return_data_size)
{
uint16_t ReadCoilsNumber = 0;
ReadCoilsNumber = *(p_read_data_string + 4) * 256 + *(p_read_data_string + 5); //计算读取线圈长度
if(ReadCoilsNumber == 0) //判定:读取线圈长度为0
{
return ModbusError(
RegisterNumberOrDataError, //错误类型
p_read_data_string, //读取数据缓存
p_retrun_data_string, //返回数据缓存
p_return_data_size); //返回数据长度
}
else //判定:线圈读取长度不为0
{
if(ReadCoilsNumber > 255) //判定:线圈读取长度超过255
{
return ModbusError(
RegisterNumberOrDataError, //错误类型
p_read_data_string, //读取数据缓存
p_retrun_data_string, //返回数据缓存
p_return_data_size); //返回数据长度
}
else //判定:线圈读取长度符合要求
{
switch(*(p_read_data_string + 2))
{
case CoilsSection_01:
if(*(p_read_data_string + 5) > CoilsSection_01_Number) //判定:读取寄存器数量过多
{
return ModbusError(
RegisterAddressError, //错误类型
p_read_data_string, //读取数据缓存
p_retrun_data_string, //返回数据缓存
p_return_data_size); //返回数据长度
}
else //判定:读取寄存器数量符合要求
{
return ReadCoilsCreateString(
p_read_data_string, //读取数据缓存
p_retrun_data_string, //返回数据缓存
p_return_data_size, //返回数据长度
CoilsSection_01, //扇区
CoilsSection_01_Number); //扇区线圈数量
}
break;
case CoilsSection_02:
if(*(p_read_data_string + 5) > CoilsSection_02_Number) //判定:读取寄存器数量过多
{
return ModbusError(
RegisterAddressError, //错误类型
p_read_data_string, //读取数据缓存
p_retrun_data_string, //返回数据缓存
p_return_data_size); //返回数据长度
}
else //判定:读取寄存器数量符合要求
{
return ReadCoilsCreateString(
p_read_data_string, //读取数据缓存
p_retrun_data_string, //返回数据缓存
p_return_data_size, //返回数据长度
CoilsSection_02, //扇区
CoilsSection_02_Number); //扇区线圈数量
}
break;
default:
return ModbusError(
RegisterAddressError, //错误类型
p_read_data_string, //读取数据缓存
p_retrun_data_string, //返回数据缓存
p_return_data_size); //返回数据长度
}
}
}
}
p_read_data_string:读取数据的首指针地址,一般填写串口接收缓存数组的数组名;
p_retrun_data_string:返回数据的首指针地址,一般填写串口发送缓存数组的数组名;
p_return_data_size:返回字节长度的存放指针地址,一般填写发送长度缓存变量的指针地址。
三、使用示例
3.1、头文件声明内容
串口部分声明内容:
#define ReceiveBufferSize 255 //接收缓存大小
extern uint8_t ReceiveBuffer[];
#define TransmitBufferSize 255 //发送缓存大小
extern uint8_t TransmitBuffer[];
extern struct UartVariable //串口变量
{
uint8_t receive_size; //接收长度
uint8_t transimt_size; //发送长度
}
U1VE;
Modbus部分声明内容 :
enum ReadOrWriteRegisterErrorType //读写寄存器错误类型
{
FunctionCodeError = 1, //错误功能码
RegisterAddressError, //寄存器地址错误
RegisterNumberOrDataError, //寄存器读取数量或写入值错误
ReadOrWriteError, //读写结果错误
};
typedef enum
{
Modbus_OK, //执行完成
Modbus_ERROR, //执行错误
Modbus_NOP, //空操作
}ModbusResult_TypeDef; //Modbus程序执行结果
typedef enum
{
CRC_Nop, //未进行CRC校验
CRC_Success, //CRC校验成功
CRC_Error, //CRC校验错误
}CrcResult_TypeDef; //CRC校验结果
3.2、工程文件定义内容
工程文件的串口定义内容:
uint8_t ReceiveBuffer[ReceiveBufferSize]; //定义ReceiveBuffer
uint8_t TransmitBuffer[TransmitBufferSize]; //定义TransmitBuffer
struct UartVariable U1VE = //串口1变量
{
0, //接收字节长度
0, //发送字节长度
};
3.3、使用示例语句
if(ModbusEnd() == CRC_Success) //判断:CRC校验成功
{
ReadCoils(ReceiveBuffer,TransmitBuffer,&U1VE.transimt_size); //执行:生成响应数据
HAL_UART_Transmit_IT(&huart1, TransmitBuffer, U1VE.transimt_size); //执行:串口发送
while(ModbusInitialize() == Modbus_ERROR); //初始化:串口1
}