1. modbus协议简介:
modbus协议基于rs485总线,采取一主多从的形式,主设备轮询各从设备信息,从设备不主动上报。
日常使用都是RTU模式,协议帧格式如下所示:
地址 功能码 寄存器地址 读取寄存器个数 寄存器数据1 ..... CrcL CrcH
1 2 3 4 5 6 7 8 9 |
|
功能码及对应的操作字长:
目前比较简单的实现了读多个保持寄存器,以及写多个保持寄存器,由于不是使用的PLC,所以寄存器地址的划分没有严格按照上表,具体地址后面解释。
2.Modbus协议编写步骤:很多设备厂家都会有自己的modbus协议,大多数都不是很标准
(1)分析板子的具体信息,编写不同的设备结构体,比如只读的结构体,可读写的结构体,保存配置信息的结构体(当主机发送改变配置信息的消息帧时,会改变相应的变量,并写入flash)
(2) modbus寄存器映射,定义保持寄存器的指针;
(2)本此编写采用轮询处理485串口接受到的数据,每次的间隔肯定大于3.5个字符时间(标准的Modbus帧间隔),所以不用但心接受不完整的情况。串口接收完成之后
会首先进行处理在串口数据中找出符合要求,接收正确的数据帧,并记录其功能码,输出帧的真实地址,就可以得到主机想要操作的从机的寄存器地址。
(3)根据上一步获取的从机寄存器地址,对保持寄存器的指针进行偏移指向,即指向不同信息结构体的首地址,此过程判断寄存器地址是否溢出。
(4)根据功能码,进行解析操作设备,读写操作就是将寄存器地址里的值直接操作指针读取出/写入。
以上过程都会判断是否错误发生,错误码如下所示:
(1)0x01 功能码错误,或者不存在
(2)0x02 寄存器地址超出范围
(3)0x04 CRC校验错误
错误回复帧的格式为:地址码 功能码|0x80 错误码 CRCL CRCH
下面就是本次用到的代码,包括将配置信息结构体读写flash:
方案1:
/******************************************* Modbus **********************************************/
uint16_t *Modbus_HoldReg = NULL;//保持寄存器
TRtuCommand g_tCurRtuCmd;
/*
AA 03 00 00 00 0A DC 16
addr cmd regH regL lenH lenL crcL crcH 读寄存器值
AA 03 14 00 00 00 00 00 00 00 00 00 03 00 01 00 00 00 18 00 1C 00 00 81 4B
addr cmd datelen ....
AA 10 00 0a 00 01 02 00 02 写寄存器值
addr cmd regH regL regNum datalen data
*/
/*====================================================================
函数名:Modbus_RegMap
功 能:根据读取寄存器的起始地址选择映射对象,将不同的地址映射到
不同的结构体数据
输入参数说明:
输出参数说明:
返回值说明:无
备 注:
====================================================================*/
void Modbus_RegMap(uint16_t wStartAddr)
{
uint16_t woffset = 0;
uint16_t wTemp = 0;
if((wStartAddr >= REG_BASE_INFO_OFFSET) && (wStartAddr < (REG_BASE_INFO_OFFSET + REG_BASE_INFO_NUM)))
{
Modbus_HoldReg = (uint16_t *)&g_tdeviceinfo;
woffset = wStartAddr - REG_BASE_INFO_OFFSET;
wTemp = REG_BASE_INFO_NUM;
}
else if(wStartAddr >= (REG_BASE_INFO_OFFSET + REG_BASE_INFO_NUM) && (wStartAddr < REG_CONFIG_INFO_OFFSET) )
{
g_tCurRtuCmd.m_byExceptionCode = 0x02; //异常码0x02超出寄存器范围
}
else if((wStartAddr >= REG_CONFIG_INFO_OFFSET) && (wStartAddr < (REG_CONFIG_INFO_OFFSET + REG_CONFIG_INFO_NUM )))
{
Modbus_HoldReg = (uint16_t *)&g_tConfigInfo;
woffset = wStartAddr - REG_CONFIG_INFO_OFFSET;
wTemp = REG_CONFIG_INFO_NUM;
}
else if(wStartAddr >= (REG_CONFIG_INFO_OFFSET + REG_CONFIG_INFO_NUM))
{
g_tCurRtuCmd.m_byExceptionCode = 0x02; //异常码0x02超出寄存器范围
}
g_tCurRtuCmd.m_wStartAddr = woffset;
g_tCurRtuCmd.m_wRegOffsetNum = wTemp;
}
/*====================================================================
函数名:DeviceInfoRefresh
功 能:更新设备运行的状态值同时更新modbus寄存器的值
输入参数说明:
输出参数说明:
返回值说明:无
备 注:
====================================================================*/
void DeviceInfoRefresh(void)
{
GetHumiAndTempVlue();
g_tdeviceinfo.m_wlightStripRly = HAL_GPIO_ReadPin(DOOR_LED_RELAY_GPIO_Port,DOOR_LED_RELAY_Pin);
g_tdeviceinfo.m_wFanRly = HAL_GPIO_ReadPin(FAN_RELAY_GPIO_Port,FAN_RELAY_Pin);
g_tdeviceinfo.m_wWarningLed1 = HAL_GPIO_ReadPin(WARNING_LED_1_GPIO_Port,WARNING_LED_1_Pin);
g_tdeviceinfo.m_wWarningLed2 = HAL_GPIO_ReadPin(WARNING_LED_2_GPIO_Port,WARNING_LED_2_Pin);
g_tdeviceinfo.m_wGMvalue = LightLevelPersenGet(); /* 光照等级 */
g_tdeviceinfo.m_wDoorLimit = HAL_GPIO_ReadPin(LIMIT_SW_DOOR_GPIO_Port,LIMIT_SW_DOOR_Pin);
g_tdeviceinfo.m_wWaterLimit = HAL_GPIO_ReadPin(WATER_MARK_GPIO_Port,WATER_MARK_Pin);
g_tdeviceinfo.m_Temp = (uint16_t)s_tsht2xInfo.m_fTemp;
g_tdeviceinfo.m_Humi = (uint16_t)s_tsht2xInfo.m_fHumi;
g_tdeviceinfo.m_vibration = Mma8452StatusGet();
}
/*====================================================================
函数名:RtuReceiveHandle
功 能:处理接受到的modbus数据,并读取/设置相应寄存器的值
输入参数说明:
pbydata :串口接收到的数据
输出参数说明:
dwLength :输入数据长度
返回值说明:无
备注:由于modubusRtu函数不支持功能码0x06(写单一寄存器),所以0x06不处理
====================================================================*/
void RtuReceiveHandle(uint8_t *pbydata,uint32_t dwLength)
{
uint8_t i;
uint16_t wCrc = 0;
uint16_t wIndex = 0, wRealLength = 0, wStartOff = 0;
uint8_t byAddr = (g_tConfigInfo.m_bydeviceAddr) & 0xFF;
g_tCurRtuCmd.m_byExceptionCode = 0;
if(pbydata == NULL || dwLength == 0)
{
TCLX_PLATFORM_DIAG(("No data received\n"));
return;
}
for(wIndex = 0; wIndex < dwLength; wIndex++)
{
if(modubusRtu(pbydata + wIndex, dwLength - wIndex, &byAddr, 1, &wRealLength, &(g_tCurRtuCmd.m_byFunCode), &wStartOff))
{
wStartOff += wIndex; /* 找到真实的Modbus数据帧 */
/* 记录命令,在主循环处理 */
g_tCurRtuCmd.m_wStartAddr = (pbydata[wStartOff + 2] << 8) + pbydata[wStartOff + 3];
Modbus_RegMap(g_tCurRtuCmd.m_wStartAddr);
TCLX_PLATFORM_DIAG(("Offset[%d] Len[%d] FunCode[0x%x] StartAddr[0x%x] RegNum[%d]\n", wStartOff, wRealLength,g_tCurRtuCmd.m_byFunCode, g_tCurRtuCmd.m_wStartAddr, g_tCurRtuCmd.m_wRegNum));
switch(g_tCurRtuCmd.m_byFunCode)
{
case 0x03:
g_tCurRtuCmd.m_wRegNum = (pbydata[wStartOff + 4] << 8) + pbydata[wStartOff + 5];
if((g_tCurRtuCmd.m_wRegNum + g_tCurRtuCmd.m_wStartAddr) <= g_tCurRtuCmd.m_wRegOffsetNum)
{
abySendData[0] = g_tConfigInfo.m_bydeviceAddr;
abySendData[1] = g_tCurRtuCmd.m_byFunCode;
abySendData[2] = g_tCurRtuCmd.m_wRegNum * 2;
for(i = 0; i < g_tCurRtuCmd.m_wRegNum; i++)
{
abySendData[3+i*2] = (Modbus_HoldReg[g_tCurRtuCmd.m_wStartAddr+i]>>8)&0xFF;// /先发送高字节--在发送低字节
abySendData[4+i*2] = (Modbus_HoldReg[g_tCurRtuCmd.m_wStartAddr+i])&0xFF; //
}
wCrc = crc16(abySendData, g_tCurRtuCmd.m_wRegNum*2 + 3);
abySendData[g_tCurRtuCmd.m_wRegNum*2 + 3] = wCrc & 0x00FF;
abySendData[g_tCurRtuCmd.m_wRegNum*2 + 4] = (wCrc >> 8) & 0x00FF;
usart_send(USART_485_INDEX, abySendData, g_tCurRtuCmd.m_wRegNum*2 + 5);
}
else
{
g_tCurRtuCmd.m_byExceptionCode = 0x02; //异常码,超出寄存范围
}
break;
case 0x06:
Modbus_HoldReg[g_tCurRtuCmd.m_wStartAddr] = pbydata[wStartOff + 4]<<8 | ((uint16_t)pbydata[wStartOff + 5]);//高字节在前
abySendData[0] = pbydata[wStartOff];
abySendData[1] = pbydata[wStartOff + 1];
abySendData[2] = pbydata[wStartOff + 2];
abySendData[3] = pbydata[wStartOff + 3];
abySendData[4] = pbydata[wStartOff + 4];
abySendData[5] = pbydata[wStartOff + 5];
wCrc = crc16(abySendData,6);
abySendData[6]=(wCrc>>8)&0xFF;
abySendData[7]=(wCrc)&0xFF;
usart_send(USART_485_INDEX, abySendData,8);
break;
case 0x10:
g_tCurRtuCmd.m_wRegNum = (pbydata[wStartOff + 4] << 8) + pbydata[wStartOff + 5];
if((g_tCurRtuCmd.m_wRegNum + g_tCurRtuCmd.m_wStartAddr) <= g_tCurRtuCmd.m_wRegOffsetNum)
{
for(i=0;i<g_tCurRtuCmd.m_wRegNum ;i++)
{
Modbus_HoldReg[g_tCurRtuCmd.m_wStartAddr+i]= pbydata[wStartOff + 7+i*2] <<8 ; //低字节在前
Modbus_HoldReg[g_tCurRtuCmd.m_wStartAddr+i]|=((uint16_t)pbydata[wStartOff + 8+i*2]); //高字节在后
}
abySendData[0] = pbydata[wStartOff];
abySendData[1] = pbydata[wStartOff + 1];
abySendData[2] = pbydata[wStartOff + 2];
abySendData[3] = pbydata[wStartOff + 3];
abySendData[4] = pbydata[wStartOff + 4];
abySendData[5] = pbydata[wStartOff + 5];
wCrc = crc16(abySendData,6);
abySendData[6]=(wCrc>>8)&0xFF;
abySendData[7]=(wCrc)&0xFF;
/* 如果配置信息发生改变就写入flash,不用做比较相等处理,写flash函数已经处理 */
writeConfigInfoToFlash(CONFIG_DATA_FLASH_ADDR,&g_tConfigInfo);
usart_send(USART_485_INDEX, abySendData,8);
}
else
{
g_tCurRtuCmd.m_byExceptionCode = 0x02; //异常码,超出寄存范围
}
break;
default:
g_tCurRtuCmd.m_byExceptionCode = 0x01; //异常码,功能码错误或者不存在
break;
}
if(g_tCurRtuCmd.m_byExceptionCode != 0)
{
TCLX_PLATFORM_DIAG(("exception code[%d]\n", g_tCurRtuCmd.m_byExceptionCode));
abySendData[0] = g_tConfigInfo.m_bydeviceAddr;
abySendData[1] = g_tCurRtuCmd.m_byFunCode + 0x80;
abySendData[2] = g_tCurRtuCmd.m_byExceptionCode;
wCrc = crc16(abySendData, 3);
abySendData[3] = wCrc & 0x00FF;
abySendData[4] = (wCrc >> 8) & 0x00FF;
usart_send(USART_485_INDEX, abySendData, 5);
}
memset(&g_tCurRtuCmd, 0, sizeof(TRtuCommand));
wIndex += (wStartOff + wRealLength - 1);
}/* switch(g_tCurRtuCmd.m_byFunCode) */
}/* if modbusRtu do.... */
usartRcvRestore(USART_485_INDEX);
}
/************************************** flash opration *****************************************/
/*====================================================================
函数名:Read_FlashData
功 能:从flash读取配置信息
输入参数说明:
FlashReadBaseAdd:配置信息基地址
输出参数说明:
DeviceCfg :配置参数
返回值说明:无
备 注:
====================================================================*/
void Read_FlashData(uint32_t FlashReadBaseAdd,DeviceConfigInfo_t *DeviceCfg)
{
if(NULL == DeviceCfg)
{
return;
}
__IO DeviceConfigInfo_t *ptPos = (__IO DeviceConfigInfo_t*)FlashReadBaseAdd;
memcpy(DeviceCfg,(const char *)ptPos,sizeof(DeviceConfigInfo_t));
}
/*====================================================================
函数名:writeConfigInfoToFlash
功 能:向flash写配置信息
输入参数说明:
FlashReadBaseAdd:配置信息基地址
输出参数说明:
DeviceCfg :配置参数
返回值说明:无
备 注:
====================================================================*/
void writeConfigInfoToFlash(uint32_t FlashWriteBaseAdd,DeviceConfigInfo_t *DeviceCfg)
{
uint8_t byIndex = 0;
uint16_t wIndex = 0;
DeviceConfigInfo_t DeviceCfgTemp = {0};
for(byIndex = 0;byIndex < 10;byIndex++)
{
Read_FlashData(FlashWriteBaseAdd,&DeviceCfgTemp);
if(0 == memcmp(&DeviceCfg,&DeviceCfgTemp,sizeof(DeviceConfigInfo_t)))
{
TCLX_PLATFORM_DIAG(("write succeed: Data equal\r\n"));
return;
}
else
{
HAL_Delay(500);
DIS_INT;
HAL_StatusTypeDef status = HAL_OK;
if(HAL_OK != (status = HAL_FLASH_Unlock()))
{
TCLX_PLATFORM_DIAG((" falsh unlock err\r\n"));
continue;
}
FLASH_EraseInitTypeDef f;
f.TypeErase = FLASH_TYPEERASE_PAGES;
f.PageAddress = (uint32_t)FlashWriteBaseAdd;
f.NbPages = 1;
uint32_t PageError = 0;
if(HAL_OK != (status = HAL_FLASHEx_Erase(&f, &PageError)))
{
if(0 != PageError)
{
TCLX_PLATFORM_DIAG(("HAL_FLASHEx_Erase:failed(%d-%d)\n",status,PageError));
HAL_FLASH_Lock();
continue;
}
}
for(wIndex = 0; wIndex < (sizeof(DeviceConfigInfo_t) / sizeof(uint32_t)); wIndex ++)
{
if(HAL_OK != (status = HAL_FLASH_Program(TYPEPROGRAM_WORD,FlashWriteBaseAdd + (wIndex * sizeof(uint32_t)) ,((uint32_t *)DeviceCfg)[wIndex])))
{
TCLX_PLATFORM_DIAG(("HAL_FLASH_Program:CONFIG_DATA_FLASH_ADDR failed(%d)\n",status));
HAL_FLASH_Lock();
continue;
}
}
if(HAL_OK != (status = HAL_FLASH_Lock()))
{
TCLX_PLATFORM_DIAG(("HAL_FLASH_Lock:HAL_FLASH_Lock(%d)\n",status));
}
EN_INT;
return ;
}
}
}
方案2:
#include "modbus.h"
#include "led.h"
#include "lcd.h"
#include "stm32f10x_tim.h"
///
u32 RS485_Baudrate=9600;//通讯波特率
u8 RS485_Parity=0;//0无校验;1奇校验;2偶校验
u8 RS485_Addr=1;//从机地址
u16 RS485_Frame_Distance=4;//数据帧最小间隔(ms),超过此时间则认为是下一帧
u8 RS485_RX_BUFF[2048];//接收缓冲区2048字节
u16 RS485_RX_CNT=0;//接收计数器
u8 RS485_FrameFlag=0;//帧结束标记
u8 RS485_TX_BUFF[2048];//发送缓冲区
u16 RS485_TX_CNT=0;//发送计数器
//Modbus寄存器和单片机寄存器的映射关系
vu32 *Modbus_InputIO[100];//输入开关量寄存器指针(这里使用的是位带操作)
vu32 *Modbus_OutputIO[100];//输出开关量寄存器指针(这里使用的是位带操作)
u16 *Modbus_HoldReg[1000];//保持寄存器指针
u32 testData1=1201,testData2=1002,testData3=2303,testData4=8204;
void Modbus_RegMap(void)
{
//输入开关量寄存器指针指向
Modbus_InputIO[0]=(vu32*)&PEin(4);//KEY0 //&PEin(4):取PE4的地址,(vu32*)&PEin(4)将PE4地址强制转换为uw32类型的地址,Modbus_InputIO[0]=(vu32*)&PEin(4); 将转换好的地址送给地址指针Modbus_InputIO[0];
Modbus_InputIO[1]=(vu32*)&PEin(3);//KEY1 //*Modbus_InputIO[0] 取出地址中的内容。
Modbus_InputIO[2]=(vu32*)&PEin(2);//KEY2
Modbus_InputIO[3]=(vu32*)&PAin(0);//KEY3
//输出开关量寄存器指针指向
Modbus_OutputIO[0]=(vu32*)&PBout(5);//LED0
Modbus_OutputIO[1]=(vu32*)&PEout(5);//LED1
//保持寄存器指针指向
Modbus_HoldReg[0]=(u16*)&testData1;//测试数据1
Modbus_HoldReg[1]=(u16*)&testData2;//((u16*)&testData1)+1;//测试数据1
Modbus_HoldReg[2]=(u16*)&testData3;//(u16*)&testData2;//测试数据2
Modbus_HoldReg[3]=(u16*)&testData4;//((u16*)&testData2)+1;//测试数据2
Modbus_HoldReg[4]=(u16*)&testData1;
Modbus_HoldReg[5]=(u16*)&testData2;
Modbus_HoldReg[6]=(u16*)&testData3;
}
/
//CRC校验 自己后面添加的
const u8 auchCRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40} ;
const u8 auchCRCLo[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,0x43, 0x83, 0x41, 0x81, 0x80, 0x40} ;
u16 CRC_Compute(u8 *puchMsg, u16 usDataLen)
{
u8 uchCRCHi = 0xFF ;
u8 uchCRCLo = 0xFF ;
u32 uIndex ;
while (usDataLen--)
{
uIndex = uchCRCHi ^ *puchMsg++ ;
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;
uchCRCLo = auchCRCLo[uIndex] ;
}
return ((uchCRCHi<< 8) | (uchCRCLo)) ;
}//uint16 crc16(uint8 *puchMsg, uint16 usDataLen)
//初始化USART2
void RS485_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;//PA2(TX)复用推挽输出
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_2);//默认高电平
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;//PA3(RX)输入上拉
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; //修改原GPIO_Mode_IPU(输入上拉)->GPIO_Mode_IN_FLOATING(浮空输入)/
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//修改PG9(RE/DE)通用推挽输出->PD7(RE/DE)通用推挽输出//
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOG,&GPIO_InitStructure);
GPIO_ResetBits(GPIOG,GPIO_Pin_9);//默认接收状态
USART_DeInit(USART2);//复位串口2
USART_InitStructure.USART_BaudRate=RS485_Baudrate;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;//收发模式
switch(RS485_Parity)
{
case 0:USART_InitStructure.USART_Parity=USART_Parity_No;break;//无校验
case 1:USART_InitStructure.USART_Parity=USART_Parity_Odd;break;//奇校验
case 2:USART_InitStructure.USART_Parity=USART_Parity_Even;break;//偶校验
}
USART_Init(USART2,&USART_InitStructure);
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//使能串口2接收中断
NVIC_InitStructure.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART2,ENABLE);//使能串口2
RS485_TX_EN=0;//默认为接收模式
Timer7_Init();//定时器7初始化,用于监视空闲时间
Modbus_RegMap();//Modbus寄存器映射
}
//定时器7初始化
void Timer7_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE); //TIM7时钟使能
//TIM7初始化设置
TIM_TimeBaseStructure.TIM_Period = RS485_Frame_Distance*10; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =7200; //设置用来作为TIMx时钟频率除数的预分频值 设置计数频率为10kHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_ITConfig( TIM7, TIM_IT_Update, ENABLE );//TIM7 允许更新中断
//TIM7中断分组配置
NVIC_InitStructure.NVIC_IRQChannel =TIM7_IRQn; //TIM7中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
//
//发送n个字节数据
//buff:发送区首地址
//len:发送的字节数
void RS485_SendData(u8 *buff,u8 len)
{
RS485_TX_EN=1;//切换为发送模式
while(len--)
{
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);//等待发送区为空
USART_SendData(USART2,*(buff++));
}
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
}
/
void USART2_IRQHandler(void)//串口2中断服务程序
{
u8 res;
u8 err;
if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
{
if(USART_GetFlagStatus(USART2,USART_FLAG_NE|USART_FLAG_FE|USART_FLAG_PE)) err=1;//检测到噪音、帧错误或校验错误
else err=0;
LED0=0;
res=USART_ReceiveData(USART2); //读接收到的字节,同时相关标志自动清除
if((RS485_RX_CNT<2047)&&(err==0))
{
RS485_RX_BUFF[RS485_RX_CNT]=res;
RS485_RX_CNT++;
TIM_ClearITPendingBit(TIM7,TIM_IT_Update);//清除定时器溢出中断
TIM_SetCounter(TIM7,0);//当接收到一个新的字节,将定时器7复位为0,重新计时(相当于喂狗)
TIM_Cmd(TIM7,ENABLE);//开始计时
}
}
}
///
//用定时器7判断接收空闲时间,当空闲时间大于指定时间,认为一帧结束
//定时器7中断服务程序
void TIM7_IRQHandler(void)
{
if(TIM_GetITStatus(TIM7,TIM_IT_Update)!=RESET)
{
TIM_ClearITPendingBit(TIM7,TIM_IT_Update);//清除中断标志
TIM_Cmd(TIM7,DISABLE);//停止定时器
RS485_TX_EN=1;//停止接收,切换为发送状态
RS485_FrameFlag=1;//置位帧结束标记
}
}
/
//RS485服务程序,用于处理接收到的数据(请在主函数中循环调用)
u16 startRegAddr;
u16 RegNum;
u16 calCRC;
void RS485_Service(void)
{
u16 recCRC;
if(RS485_FrameFlag==1)
{
if(RS485_RX_BUFF[0]==RS485_Addr)//地址正确
{
if((RS485_RX_BUFF[1]==01)||(RS485_RX_BUFF[1]==02)||(RS485_RX_BUFF[1]==03)||(RS485_RX_BUFF[1]==05)||(RS485_RX_BUFF[1]==06)||(RS485_RX_BUFF[1]==15)||(RS485_RX_BUFF[1]==16))//功能码正确
{
startRegAddr=(((u16)RS485_RX_BUFF[2])<<8)|RS485_RX_BUFF[3];//获取寄存器起始地址
if(startRegAddr<1000)//寄存器地址在范围内
{
calCRC=CRC_Compute(RS485_RX_BUFF,RS485_RX_CNT-2);//计算所接收数据的CRC
recCRC=RS485_RX_BUFF[RS485_RX_CNT-1]|(((u16)RS485_RX_BUFF[RS485_RX_CNT-2])<<8);//接收到的CRC(低字节在前,高字节在后)
if(calCRC==recCRC)//CRC校验正确
{
///显示用
LCD_ShowxNum(10,230,RS485_RX_BUFF[0],3,16,0X80);//显示数据
LCD_ShowxNum(42,230,RS485_RX_BUFF[1],3,16,0X80);//显示数据
LCD_ShowxNum(74,230,RS485_RX_BUFF[2],3,16,0X80);//显示数据
LCD_ShowxNum(106,230,RS485_RX_BUFF[3],3,16,0X80);//显示数据
LCD_ShowxNum(138,230,RS485_RX_BUFF[4],3,16,0X80);//显示数据
LCD_ShowxNum(170,230,RS485_RX_BUFF[5],3,16,0X80);//显示数据
///
/
switch(RS485_RX_BUFF[1])//根据不同的功能码进行处理
{
case 2://读输入开关量
{
Modbus_02_Solve();
break;
}
case 1://读输出开关量
{
Modbus_01_Solve();
break;
}
case 5://写单个输出开关量
{
Modbus_05_Solve();
break;
}
case 15://写多个输出开关量
{
Modbus_15_Solve();
break;
}
case 03: //读多个寄存器
{
Modbus_03_Solve();
break;
}
case 06: //写单个寄存器
{
Modbus_06_Solve();
break;
}
case 16: //写多个寄存器
{
Modbus_16_Solve();
break;
}
}
//
}
else//CRC校验错误
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x04; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
else//寄存器地址超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
else//功能码错误
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x01; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
RS485_FrameFlag=0;//复位帧结束标志
RS485_RX_CNT=0;//接收计数器清零
RS485_TX_EN=0;//开启接收模式
}
}
//Modbus功能码02处理程序/程序已验证OK -----必须先配置PE4 PE3 PE2 PA0 初始化按键才可以OK KEY_Init();
//读输入开关量
void Modbus_02_Solve(void)
{
u16 ByteNum;
u16 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<100)//寄存器地址+数量在范围内
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
ByteNum=RegNum/8;//字节数
if(RegNum%8) ByteNum+=1;//如果位数还有余数,则字节数+1
RS485_TX_BUFF[2]=ByteNum;//返回要读取的字节数
for(i=0;i<RegNum;i++)
{
if(i%8==0) RS485_TX_BUFF[3+i/8]=0x00;
RS485_TX_BUFF[3+i/8]>>=1;//低位先发送
RS485_TX_BUFF[3+i/8]|=((*Modbus_InputIO[startRegAddr+i])<<7)&0x80;
if(i==RegNum-1)//发送到最后一个位了
{
if(RegNum%8) RS485_TX_BUFF[3+i/8]>>=8-(RegNum%8);//如果最后一个字节还有余数,则剩余MSB填充0
}
}
calCRC=CRC_Compute(RS485_TX_BUFF,ByteNum+3);
RS485_TX_BUFF[ByteNum+3]=(calCRC>>8)&0xFF;
RS485_TX_BUFF[ByteNum+4]=(calCRC)&0xFF;
RS485_SendData(RS485_TX_BUFF,ByteNum+5);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
//Modbus功能码01处理程序 ///程序已验证OK
//读输出开关量
void Modbus_01_Solve(void)
{
u16 ByteNum;
u16 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<100)//寄存器地址+数量在范围内
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
ByteNum=RegNum/8;//字节数
if(RegNum%8) ByteNum+=1;//如果位数还有余数,则字节数+1
RS485_TX_BUFF[2]=ByteNum;//返回要读取的字节数
for(i=0;i<RegNum;i++)
{
if(i%8==0) RS485_TX_BUFF[3+i/8]=0x00;
RS485_TX_BUFF[3+i/8]>>=1;//低位先发送
RS485_TX_BUFF[3+i/8]|=((*Modbus_OutputIO[startRegAddr+i])<<7)&0x80;
if(i==RegNum-1)//发送到最后一个位了
{
if(RegNum%8) RS485_TX_BUFF[3+i/8]>>=8-(RegNum%8);//如果最后一个字节还有余数,则剩余MSB填充0
}
}
calCRC=CRC_Compute(RS485_TX_BUFF,ByteNum+3);
RS485_TX_BUFF[ByteNum+3]=(calCRC>>8)&0xFF;
RS485_TX_BUFF[ByteNum+4]=(calCRC)&0xFF;
RS485_SendData(RS485_TX_BUFF,ByteNum+5);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
//Modbus功能码05处理程序 ///程序已验证OK
//写单个输出开关量
void Modbus_05_Solve(void)
{
if(startRegAddr<100)//寄存器地址在范围内
{
if((RS485_RX_BUFF[4]==0xFF)||(RS485_RX_BUFF[5]==0xFF)) *Modbus_OutputIO[startRegAddr]=0x01;
else *Modbus_OutputIO[startRegAddr]=0x00;
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];
calCRC=CRC_Compute(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=(calCRC>>8)&0xFF;
RS485_TX_BUFF[7]=(calCRC)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
}
else//寄存器地址超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
//Modbus功能码15处理程序 //程序已验证OK
//写多个输出开关量
void Modbus_15_Solve(void)
{
u16 i;
RegNum=(((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<100)//寄存器地址+数量在范围内
{
for(i=0;i<RegNum;i++)
{
if(RS485_RX_BUFF[7+i/8]&0x01) *Modbus_OutputIO[startRegAddr+i]=0x01;
else *Modbus_OutputIO[startRegAddr+i]=0x00;
RS485_RX_BUFF[7+i/8]>>=1;//从低位开始
}
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];
calCRC=CRC_Compute(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=(calCRC>>8)&0xFF;
RS485_TX_BUFF[7]=(calCRC)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
//Modbus功能码03处理程序///已验证程序OK
//读保持寄存器
void Modbus_03_Solve(void)
{
u8 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|RS485_RX_BUFF[5];//获取寄存器数量
if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RegNum*2;
for(i=0;i<RegNum;i++)
{
RS485_TX_BUFF[3+i*2]=(*Modbus_HoldReg[startRegAddr+i]>>8)&0xFF;// /先发送高字节--在发送低字节
RS485_TX_BUFF[4+i*2]=(*Modbus_HoldReg[startRegAddr+i])&0xFF; //
}
calCRC=CRC_Compute(RS485_TX_BUFF,RegNum*2+3);
RS485_TX_BUFF[RegNum*2+3]=(calCRC>>8)&0xFF; //CRC高地位不对吗? // 先高后低
RS485_TX_BUFF[RegNum*2+4]=(calCRC)&0xFF;
RS485_SendData(RS485_TX_BUFF,RegNum*2+5);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}
//Modbus功能码06处理程序 //已验证程序OK
//写单个保持寄存器
void Modbus_06_Solve(void)
{
*Modbus_HoldReg[startRegAddr]=RS485_RX_BUFF[4]<<8;//高字节在前 修改为高字节在前,低字节在后
*Modbus_HoldReg[startRegAddr]|=((u16)RS485_RX_BUFF[5]);//低字节在后
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];
calCRC=CRC_Compute(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=(calCRC>>8)&0xFF;
RS485_TX_BUFF[7]=(calCRC)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
}
//Modbus功能码16处理程序 /已验证程序OK
//写多个保持寄存器
void Modbus_16_Solve(void)
{
u8 i;
RegNum= (((u16)RS485_RX_BUFF[4])<<8)|((RS485_RX_BUFF[5]));//获取寄存器数量
if((startRegAddr+RegNum)<1000)//寄存器地址+数量在范围内
{
for(i=0;i<RegNum;i++)
{
*Modbus_HoldReg[startRegAddr+i]=RS485_RX_BUFF[7+i*2]; //低字节在前 /// 低字节在前,高字节在后正常
*Modbus_HoldReg[startRegAddr+i]|=((u16)RS485_RX_BUFF[8+i*2])<<8; //高字节在后
}
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1];
RS485_TX_BUFF[2]=RS485_RX_BUFF[2];
RS485_TX_BUFF[3]=RS485_RX_BUFF[3];
RS485_TX_BUFF[4]=RS485_RX_BUFF[4];
RS485_TX_BUFF[5]=RS485_RX_BUFF[5];
calCRC=CRC_Compute(RS485_TX_BUFF,6);
RS485_TX_BUFF[6]=(calCRC>>8)&0xFF;
RS485_TX_BUFF[7]=(calCRC)&0xFF;
RS485_SendData(RS485_TX_BUFF,8);
}
else//寄存器地址+数量超出范围
{
RS485_TX_BUFF[0]=RS485_RX_BUFF[0];
RS485_TX_BUFF[1]=RS485_RX_BUFF[1]|0x80;
RS485_TX_BUFF[2]=0x02; //异常码
RS485_SendData(RS485_TX_BUFF,3);
}
}