- //----------------------------------------------------------------------------//
- //此代码只支持作为Modbus从站设备的Modbus RTU模式
- //
- //支持的功能码:
- //0x03 读保持寄存器(读多个保持寄存器的值,有效地位为0-99)
- //0x06 写单个寄存器(写入一个寄存器的值,有效地址为0-99)
- //0x10 写多个寄存器(写入多个寄存器的值,有效地址为0-99)
- //
- //支持的异常码:
- //0x01 非法功能码(不支持的功能码)
- //0x02 非法数据地址(起始地址不在有效范围内)
- //0x03 非法数据值(在起始地址的基础上,数量是不合法的)
- //----------------------------------------------------------------------------//
- #include "Modbus.h"
- #include "main.h"
- /* 变量定义 ------------------------------------------------------------------*/
- uint8_t Modbus_Send_Buff[Modbus_Max_Send_Buff]; //发送数据缓冲区
- uint8_t Modbus_Rcv_Buff[Modbus_Max_Rcv_Buff]; //接收数据缓冲区
- uint8_t Modbus_Timeout_Cnt; //定时器中断计数
- uint8_t Modbus_Rcv_Cnt; //接收字节计数
- uint8_t Modbus_Rcv_flag; //设备进入接收状态标志
- uint8_t Modbus_Cmd_flag; //设备进入命令解析状态标志
- uint8_t Modbus_Exe_flag; //设备进入命令执行状态标志
- uint8_t Modbus_Function; //从站设备需执行的功能
- uint16_t HoldingReg[100] = {0x0123, 0x4567, 0x89AB, 0xCDEF}; //保持寄存器
- /* 函数定义 ------------------------------------------------------------------*/
- //----------------------------------------------------------------------------//
- //函数功能:逐位计算法CRC16校验,在Modbus中CRC结果要进行高低字节交换,即低字节在前,高字节在后
- //入口参数:puchMsg是要进行CRC校验的消息;usDataLen是消息中字节数
- //出口参数:计算出来的CRC校验码,16位长度
- //最后修改:2015.11.29
- //备注:
- //----------------------------------------------------------------------------//
- uint16_t Modbus_CRC16(uint8_t *puchMsg, uint8_t usDataLen)
- {
- uint16_t CRC_Cal = 0xFFFF;
- uint8_t CRC_High, CRC_Low;
- uint8_t i, j;
-
- for(j = 0; j < usDataLen; j++)
- {
- CRC_Cal = CRC_Cal ^ *puchMsg++;
-
- for (i = 0; i < 8; i++)
- {
- if((CRC_Cal & 0x0001) == 0x0001)
- {
- CRC_Cal = CRC_Cal >> 1;
- CRC_Cal = CRC_Cal ^ 0xA001;
- }
- else
- {
- CRC_Cal = CRC_Cal >> 1;
- }
- }
- }
-
- CRC_High = (uint8_t)(CRC_Cal >> 8);
- CRC_Low = (uint8_t)(CRC_Cal & 0x00FF);
-
- return (CRC_Low << 8 | CRC_High);
-
- // return CRC_Cal;
- }
- //----------------------------------------------------------------------------//
- //函数功能:Modbus初始化
- //入口参数:ID是从站站号
- //出口参数:无
- //最后修改:2015.11.20
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_Init(void)
- {
- uint16_t i;
-
- //----------------------------------------------------------//
- //Modbus相关变量初始化
- //----------------------------------------------------------//
- Modbus_Timeout_Cnt = 0;
- Modbus_Rcv_Cnt = 0;
- Modbus_Rcv_flag = 0;
- Modbus_Cmd_flag = 0;
- Modbus_Exe_flag = 0;
-
- for(i = 0; i < Modbus_Max_Rcv_Buff; i++) //清除接收缓冲区
- {
- Modbus_Rcv_Buff[i] = '\0';
- }
-
- for(i = 0; i < Modbus_Max_Send_Buff; i++) //清除发送缓冲区
- {
- Modbus_Send_Buff[i] = '\0';
- }
-
- //----------------------------------------------------------//
- //TIM2定时器使能
- //----------------------------------------------------------//
- TIM_Cmd(TIM2, ENABLE);
- }
- //----------------------------------------------------------------------------//
- //函数功能:Modbus命令解析函数
- //入口参数:无
- //出口参数:无
- //最后修改:2015.12.11
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_Cmd(void)
- {
- uint8_t Modbus_CRC_Rcv_Hi; //接收到的ModbusCRC校验码高字节
- uint8_t Modbus_CRC_Rcv_Lo; //接收到的ModbusCRC校验码低字节
- uint16_t Modbus_CRC_Rcv; //接收到的ModbusCRC校验码
- uint16_t Modbus_CRC_Cal; //根据接收到的数据计算出来的CRC值
-
- //----------------------------------------------------------//
- //开始命令解析
- //----------------------------------------------------------//
- if(Modbus_Cmd_flag == 1)
- {
- if(Modbus_Rcv_Cnt > 4) //如果接收到的一帧的字节数大于4 首先确保帧的长度在正常范围
- {
- Modbus_CRC_Rcv_Lo = Modbus_Rcv_Buff[Modbus_Rcv_Cnt - 2]; //接收到的ModbusCRC校验码低字节
- Modbus_CRC_Rcv_Hi = Modbus_Rcv_Buff[Modbus_Rcv_Cnt - 1]; //接收到的ModbusCRC校验码高字节
- Modbus_CRC_Rcv = (uint16_t)(Modbus_CRC_Rcv_Lo << 8 | Modbus_CRC_Rcv_Hi); //接收到的ModbusCRC校验码(16位)
- Modbus_CRC_Cal = Modbus_CRC16(Modbus_Rcv_Buff, Modbus_Rcv_Cnt - 2); //根据接收到的数据计算CRC值
-
- if(Modbus_CRC_Cal == Modbus_CRC_Rcv) //如果计算的CRC值与接受的CRC值相等
- {
- //USART_SendByte(USART1, 0xAC);
- if(Slave_Address == Modbus_Rcv_Buff[0]) //如果是本机地址
- {
- switch(Modbus_Rcv_Buff[1]) //用switch分支语句来确定功能
- {
- case Modbus_ReadHoldingReg: //如果是读保存寄存器
- Modbus_Function = Modbus_ReadHoldingReg; //将从站设备需执行的功能赋值为读保存寄存器
- Modbus_Exe_flag = 1; //设备进入命令执行状态
- break; //跳出分支语句
-
- case Modbus_WriteSingleReg:
- Modbus_Function = Modbus_WriteSingleReg; //将从站设备需执行的功能赋值为写单个寄存器
- Modbus_Exe_flag = 1; //设备进入命令执行状态
- break; //跳出分支语句
-
- case Modbus_WriteMultipleReg:
- Modbus_Function = Modbus_WriteMultipleReg; //将从站设备需执行的功能赋值为写多个寄存器
- Modbus_Exe_flag = 1; //设备进入命令执行状态
- break; //跳出分支语句
-
- default:
- Modbus_ErrorHandling(0x01); //所有功能码都不符合,则返回功能码错误异常响应报文
- return;
- }
- }
-
- else //否则清空接收数据缓冲区和发送数据缓冲区
- {
- Modbus_ClearBuff();
- }
- }
-
- else //否则清空接收数据缓冲区和发送数据缓冲区
- {
- Modbus_ClearBuff();
- }
-
- }
-
- else //否则清空接收数据缓冲区和发送数据缓冲区
- {
- Modbus_ClearBuff();
- }
-
- Modbus_Cmd_flag = 0; //设备退出命令解析状态标志
- }
- }
- //----------------------------------------------------------------------------//
- //函数功能:Modbus命令执行函数
- //入口参数:无
- //出口参数:无
- //最后修改:2015.12.6
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_Exe(void)
- {
- if(Modbus_Exe_flag == 1)
- {
- switch(Modbus_Function)
- {
- case Modbus_ReadHoldingReg:
- Modbus_ReadHoldingReg_Process();
- break;
-
- case Modbus_WriteSingleReg:
- Modbus_WriteSingleReg_Process();
- break;
-
- case Modbus_WriteMultipleReg:
- Modbus_WriteMultipleReg_Process();
- break;
-
- }
- Modbus_Exe_flag = 0;
- }
- }
- //----------------------------------------------------------------------------//
- //函数功能:功能码0x03,读保持寄存器
- //入口参数:无
- //出口参数:无
- //最后修改:2015.12.5
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_ReadHoldingReg_Process(void)
- {
- uint8_t Send_Cnt; //发送字节数量
- uint16_t StartAddress_Reg; //要读取的寄存器起始地址
- uint16_t Num_Reg; //要读取的寄存器的数量
- uint16_t CRC_Cal; //CRC校验码
- uint16_t i, j; //临时变量
-
- StartAddress_Reg = Modbus_Rcv_Buff[2] << 8 | Modbus_Rcv_Buff[3]; //从接收数据缓冲区得到要读取的寄存器起始地址
- Num_Reg = Modbus_Rcv_Buff[4] << 8 | Modbus_Rcv_Buff[5]; //从接收数据缓冲区得到要读取的寄存器数量
-
- if(StartAddress_Reg < 100) //寄存器起始地址在正确范围内
- {
- if(StartAddress_Reg + Num_Reg < 100 && Num_Reg > 0) //起始地址+寄存器数量位于正确范围内 并且 寄存器数量正确
- {
- Send_Cnt = 3 + (Num_Reg << 1) + 2; //计算发送字节数量
-
- Modbus_Send_Buff[0] = Slave_Address; //从站地址
- Modbus_Send_Buff[1] = Modbus_ReadHoldingReg; //功能码
- Modbus_Send_Buff[2] = Num_Reg << 1; //寄存器字节数量 等于 寄存器数量乘2
-
- for(i = StartAddress_Reg, j = 3; i < StartAddress_Reg + Num_Reg; i++, j += 2) //读取寄存器的数据
- {
- Modbus_Send_Buff[j] = (uint8_t)(HoldingReg[i] >> 8);
- Modbus_Send_Buff[j + 1] = (uint8_t)(HoldingReg[i] & 0x00FF);
- }
-
- CRC_Cal = Modbus_CRC16(Modbus_Send_Buff, 3 + (Num_Reg << 1)); //计算发送数据的CRC校验码
- Modbus_Send_Buff[3 + (Num_Reg << 1)] = (uint8_t)(CRC_Cal >> 8); //先是低字节
- Modbus_Send_Buff[3 + (Num_Reg << 1) + 1] = (uint8_t)(CRC_Cal & 0x00FF); //后是高字节
-
- USART_SendString(USART1, Modbus_Send_Buff, Send_Cnt); //发送响应报文
-
- Modbus_ClearBuff(); //清空接收数据缓冲区和发送数据缓冲区
- }
-
- else
- {
- Modbus_ErrorHandling(0x03); //非法数据值
- }
- }
-
- else
- {
- Modbus_ErrorHandling(0x02); //非法数据地址
- }
-
- }
- //----------------------------------------------------------------------------//
- //函数功能:功能码0x06,写单个寄存器
- //入口参数:无
- //出口参数:无
- //最后修改:2015.12.6
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_WriteSingleReg_Process(void)
- {
- uint8_t Send_Cnt; //发送字节数量
- uint16_t Address_Reg; //要写入的寄存器地址
- uint16_t Value_Reg; //要写入的寄存器值
- uint16_t CRC_Cal; //CRC校验码
-
- Address_Reg = Modbus_Rcv_Buff[2] << 8 | Modbus_Rcv_Buff[3]; //从接收数据缓冲区得到要写入的寄存器地址
- Value_Reg = Modbus_Rcv_Buff[4] << 8 | Modbus_Rcv_Buff[5]; //从接收数据缓冲区得到要写入的寄存器值
-
- if(Address_Reg < 100) //寄存器起始地址在正确范围内
- {
- Send_Cnt = 6 + 2; //计算发送字节数量
-
- HoldingReg[Address_Reg] = Value_Reg; //将要写入的寄存器值写入寄存器
-
- Modbus_Send_Buff[0] = Slave_Address; //从站地址
- Modbus_Send_Buff[1] = Modbus_WriteSingleReg; //功能码
- Modbus_Send_Buff[2] = (uint8_t)(Address_Reg >> 8); //寄存器地址高字节
- Modbus_Send_Buff[3] = (uint8_t)(Address_Reg & 0x00FF); //寄存器地址低字节
- Modbus_Send_Buff[4] = (uint8_t)(HoldingReg[Address_Reg] >> 8); //寄存器值高字节
- Modbus_Send_Buff[5] = (uint8_t)(HoldingReg[Address_Reg] & 0x00FF); //寄存器值低字节
-
- CRC_Cal = Modbus_CRC16(Modbus_Send_Buff, 6); //计算发送数据的CRC校验码
- Modbus_Send_Buff[6] = (uint8_t)(CRC_Cal >> 8); //先是低字节
- Modbus_Send_Buff[7] = (uint8_t)(CRC_Cal & 0x00FF); //后是高字节
-
- USART_SendString(USART1, Modbus_Send_Buff, Send_Cnt); //发送响应报文
-
- Modbus_ClearBuff(); //清空接收数据缓冲区和发送数据缓冲区
- }
-
- else
- {
- Modbus_ErrorHandling(0x02); //非法数据地址
- }
- }
- //----------------------------------------------------------------------------//
- //函数功能:功能码0x10,写多个寄存器
- //入口参数:无
- //出口参数:无
- //最后修改:2015.12.9
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_WriteMultipleReg_Process(void)
- {
- uint8_t Send_Cnt; //发送字节数量
- uint16_t StartAddress_Reg; //要写入的寄存器起始地址
- uint16_t Num_Reg; //要写入的寄存器的数量
- uint16_t CRC_Cal; //CRC校验码
- uint16_t i, j; //临时变量
-
- StartAddress_Reg = Modbus_Rcv_Buff[2] << 8 | Modbus_Rcv_Buff[3]; //从接收数据缓冲区得到要写入的寄存器起始地址
- Num_Reg = Modbus_Rcv_Buff[4] << 8 | Modbus_Rcv_Buff[5]; //从接收数据缓冲区得到要写入的寄存器数量
-
- if(StartAddress_Reg < 100) //寄存器起始地址在正确范围内
- {
- if(StartAddress_Reg + Num_Reg < 100 && Num_Reg > 0) //起始地址+寄存器数量位于正确范围内 并且 寄存器数量正确
- {
- for(i = StartAddress_Reg, j = 7; i < StartAddress_Reg + Num_Reg; i++, j += 2) //将要写入的寄存器值写入寄存器
- {
- HoldingReg[i] = Modbus_Rcv_Buff[j] << 8 | Modbus_Rcv_Buff[j + 1];
- }
-
- Send_Cnt = 6 + 2;
-
- Modbus_Send_Buff[0] = Slave_Address; //从站地址
- Modbus_Send_Buff[1] = Modbus_WriteMultipleReg; //功能码
- Modbus_Send_Buff[2] = (uint8_t)(StartAddress_Reg >> 8); //寄存器起始地址高字节
- Modbus_Send_Buff[3] = (uint8_t)(StartAddress_Reg & 0x00FF); //寄存器起始地址低字节
- Modbus_Send_Buff[4] = (uint8_t)(Num_Reg >> 8); //寄存器数量高字节
- Modbus_Send_Buff[5] = (uint8_t)(Num_Reg & 0x00FF); //寄存器数量低字节
-
- CRC_Cal = Modbus_CRC16(Modbus_Send_Buff, 6); //计算发送数据的CRC校验码
- Modbus_Send_Buff[6] = (uint8_t)(CRC_Cal >> 8); //先是低字节
- Modbus_Send_Buff[7] = (uint8_t)(CRC_Cal & 0x00FF); //后是高字节
-
- USART_SendString(USART1, Modbus_Send_Buff, Send_Cnt); //发送响应报文
-
- Modbus_ClearBuff(); //清空接收数据缓冲区和发送数据缓冲区
- }
-
- else
- {
- Modbus_ErrorHandling(0x03);
- }
- }
-
- else
- {
- Modbus_ErrorHandling(0x02);
- }
- }
- //----------------------------------------------------------------------------//
- //函数功能:错误处理
- //入口参数:ErrorType是错误类型
- //出口参数:无
- //最后修改:2015.12.11
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_ErrorHandling(uint8_t ErrorType)
- {
- uint16_t CRC_Cal; //CRC校验码
-
- switch(ErrorType) //用switch分支语句来确定Modbus异常码
- {
- case 0x01: //非法功能码
- Modbus_Send_Buff[0] = Slave_Address; //从站地址
- Modbus_Send_Buff[1] = Modbus_Rcv_Buff[1] + 0x80; //异常功能码
- Modbus_Send_Buff[2] = 0x01; //异常码
- CRC_Cal = Modbus_CRC16(Modbus_Send_Buff, 3); //计算发送数据的CRC校验码
- Modbus_Send_Buff[3] = (uint8_t)(CRC_Cal >> 8); //先是低字节
- Modbus_Send_Buff[4] = (uint8_t)(CRC_Cal & 0x00FF); //后是高字节
-
- USART_SendString(USART1, Modbus_Send_Buff, 5); //发送异常响应报文
- break;
-
- case 0x02: //非法数据地址
- Modbus_Send_Buff[0] = Slave_Address; //从站地址
- Modbus_Send_Buff[1] = Modbus_Rcv_Buff[1] + 0x80; //异常功能码
- Modbus_Send_Buff[2] = 0x02; //异常码
- CRC_Cal = Modbus_CRC16(Modbus_Send_Buff, 3); //计算发送数据的CRC校验码
- Modbus_Send_Buff[3] = (uint8_t)(CRC_Cal >> 8); //先是低字节
- Modbus_Send_Buff[4] = (uint8_t)(CRC_Cal & 0x00FF); //后是高字节
-
- USART_SendString(USART1, Modbus_Send_Buff, 5); //发送异常响应报文
- break;
-
- case 0x03: //非法数据值
- Modbus_Send_Buff[0] = Slave_Address; //从站地址
- Modbus_Send_Buff[1] = Modbus_Rcv_Buff[1] + 0x80; //异常功能码
- Modbus_Send_Buff[2] = 0x03; //异常码
- CRC_Cal = Modbus_CRC16(Modbus_Send_Buff, 3); //计算发送数据的CRC校验码
- Modbus_Send_Buff[3] = (uint8_t)(CRC_Cal >> 8); //先是低字节
- Modbus_Send_Buff[4] = (uint8_t)(CRC_Cal & 0x00FF); //后是高字节
-
- USART_SendString(USART1, Modbus_Send_Buff, 5); //发送异常响应报文
- break;
-
- // default:
- // return;
-
- }
-
- Modbus_ClearBuff(); //清空接收数据缓冲区和发送数据缓冲区
- }
- //----------------------------------------------------------------------------//
- //函数功能:清空接收数据缓冲区和发送数据缓冲区
- //入口参数:无
- //出口参数:无
- //最后修改:2015.12.5
- //备注:
- //----------------------------------------------------------------------------//
- void Modbus_ClearBuff(void)
- {
- uint16_t i;
-
- for(i = 0; i < Modbus_Max_Rcv_Buff; i++) //清除接收缓冲区
- {
- Modbus_Rcv_Buff[i] = '\0';
- }
- Modbus_Rcv_Cnt = 0; //接收字节计数清0
-
- for(i = 0; i < Modbus_Max_Send_Buff; i++) //清除发送缓冲区
- {
- Modbus_Send_Buff[i] = '\0';
- }
-
- }
- //----------------------------------------------------------------------------//
- //函数功能:把一个字节的高4位十六进制数存到另一个字节的低4位里
- //入口参数:一个字节的数据
- //出口参数:另一个字节
- //最后修改:2015.11.28
- //备注:
- //----------------------------------------------------------------------------//
- uint8_t High4BitsToOneByte(uint8_t Byte)
- {
- uint8_t tempByte;
-
- tempByte = (Byte >> 4) & 0x0F;
-
- return tempByte;
- }
- //----------------------------------------------------------------------------//
- //函数功能:把一个字节的低4位十六进制数存到另一个字节的低4位里
- //入口参数:一个字节的数据
- //出口参数:另一个字节
- //最后修改:2015.11.28
- //备注:
- //----------------------------------------------------------------------------//
- uint8_t Low4BitsToOneByte(uint8_t Byte)
- {
- uint8_t tempByte;
-
- tempByte = Byte & 0x0F;
-
- return tempByte;
- }
- //----------------------------------------------------------------------------//
- //函数功能:把低4位16进制数转换为在OLED字库上对应的0~9和A~F
- //入口参数:HexByte是低4位16进制数
- //出口参数:OLED字库上对应的0~9和A~F
- //最后修改:2015.11.28
- //备注:
- //----------------------------------------------------------------------------//
- uint8_t HexToOLEDAsc(uint8_t HexByte)
- {
- if((HexByte >= 0x00) && (HexByte <= 0x09)) //数字0~9
- {
- HexByte += 0x30;
- }
- else if((HexByte >= 0x0A) && (HexByte <= 0x0F)) //数字A~F
- {
- HexByte += 0x37;
- }
- else
- {
- HexByte = 0xff;
- }
-
- return HexByte;
- }
复制代码 |